diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index e05866353..f9c2eac13 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -26,7 +26,6 @@ import kotlinx.coroutines.sync.withLock import java.io.* import java.util.* import java.util.concurrent.TimeUnit -import kotlin.system.exitProcess const val TAG = "SIMPLEX" @@ -72,7 +71,7 @@ class SimplexApp: Application(), LifecycleEventObserver { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { Log.d(TAG, "onStateChanged: $event") - withApi { + withBGApi { when (event) { Lifecycle.Event.ON_START -> { isAppOnForeground = true diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt index 8dfe04b3d..bfa1770d9 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt @@ -104,7 +104,7 @@ class SimplexService: Service() { if (wakeLock != null || isStartingService) return val self = this isStartingService = true - withApi { + withBGApi { val chatController = ChatController waitDbMigrationEnds(chatController) try { @@ -114,7 +114,7 @@ class SimplexService: Service() { Log.w(chat.simplex.app.TAG, "SimplexService: problem with the database: $chatDbStatus") showPassphraseNotification(chatDbStatus) safeStopService() - return@withApi + return@withBGApi } saveServiceState(self, ServiceState.STARTED) wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt index 3aa4a9261..2849e0403 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.android.kt @@ -13,8 +13,7 @@ import chat.simplex.common.model.ChatItem import chat.simplex.common.model.MsgContent import chat.simplex.common.platform.FileChooserLauncher import chat.simplex.common.platform.saveImage -import chat.simplex.common.views.helpers.SharedContent -import chat.simplex.common.views.helpers.withApi +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import com.google.accompanist.permissions.rememberPermissionState import dev.icerock.moko.resources.compose.painterResource @@ -37,7 +36,7 @@ actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserL writePermissionState.launchPermissionRequest() } } - is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> withApi { saveFileLauncher.launch(cItem.file?.fileName ?: "") } + is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> withBGApi { saveFileLauncher.launch(cItem.file?.fileName ?: "") } else -> {} } showMenu.value = false diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index e02c011fa..932d259df 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -102,7 +102,7 @@ fun AppearanceScope.AppearanceLayout( val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") } LangSelector(state) { state.value = it - withApi { + withBGApi { delay(200) val activity = context as? Activity if (activity != null) { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt index 9b376bb9d..f2c3d86ab 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.android.kt @@ -5,8 +5,7 @@ import androidx.compose.runtime.Composable import androidx.work.WorkManager import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* -import chat.simplex.common.views.helpers.AlertManager -import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import com.jakewharton.processphoenix.ProcessPhoenix import dev.icerock.moko.resources.compose.painterResource @@ -15,7 +14,7 @@ import dev.icerock.moko.resources.compose.stringResource @Composable actual fun SettingsSectionApp( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), + showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index d1927e405..96437ad33 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -110,6 +110,7 @@ class AppPreferences { val chatStopped = mkBoolPreference(SHARED_PREFS_CHAT_STOPPED, false) val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false) val showInternalErrors = mkBoolPreference(SHARED_PREFS_SHOW_INTERNAL_ERRORS, false) + val showSlowApiCalls = mkBoolPreference(SHARED_PREFS_SHOW_SLOW_API_CALLS, false) val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false) val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false) val networkProxyHostPort = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, "localhost:9050") @@ -279,6 +280,7 @@ class AppPreferences { private const val SHARED_PREFS_CHAT_STOPPED = "ChatStopped" private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools" private const val SHARED_PREFS_SHOW_INTERNAL_ERRORS = "ShowInternalErrors" + private const val SHARED_PREFS_SHOW_SLOW_API_CALLS = "ShowSlowApiCalls" private const val SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE = "TerminalAlwaysVisible" private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy" private const val SHARED_PREFS_NETWORK_PROXY_HOST_PORT = "NetworkProxyHostPort" @@ -464,19 +466,19 @@ object ChatController { suspend fun sendCmd(rhId: Long?, cmd: CC): CR { val ctrl = ctrl ?: throw Exception("Controller is not initialized") - return withContext(Dispatchers.IO) { - val c = cmd.cmdString - chatModel.addTerminalItem(TerminalItem.cmd(rhId, cmd.obfuscated)) - Log.d(TAG, "sendCmd: ${cmd.cmdType}") - val json = if (rhId == null) chatSendCmd(ctrl, c) else chatSendRemoteCmd(ctrl, rhId.toInt(), c) - val r = APIResponse.decodeStr(json) - Log.d(TAG, "sendCmd response type ${r.resp.responseType}") - if (r.resp is CR.Response || r.resp is CR.Invalid) { - Log.d(TAG, "sendCmd response json $json") - } - chatModel.addTerminalItem(TerminalItem.resp(rhId, r.resp)) - r.resp + //return withContext(Dispatchers.IO) { + val c = cmd.cmdString + chatModel.addTerminalItem(TerminalItem.cmd(rhId, cmd.obfuscated)) + Log.d(TAG, "sendCmd: ${cmd.cmdType}") + val json = if (rhId == null) chatSendCmd(ctrl, c) else chatSendRemoteCmd(ctrl, rhId.toInt(), c) + val r = APIResponse.decodeStr(json) + Log.d(TAG, "sendCmd response type ${r.resp.responseType}") + if (r.resp is CR.Response || r.resp is CR.Invalid) { + Log.d(TAG, "sendCmd response json $json") } + chatModel.addTerminalItem(TerminalItem.resp(rhId, r.resp)) + return r.resp + //} } private fun recvMsg(ctrl: ChatCtrl): APIResponse? { @@ -1665,7 +1667,8 @@ object ChatController { ((mc is MsgContent.MCImage && file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV) || (mc is MsgContent.MCVideo && file.fileSize <= MAX_VIDEO_SIZE_AUTO_RCV) || (mc is MsgContent.MCVoice && file.fileSize <= MAX_VOICE_SIZE_AUTO_RCV && file.fileStatus !is CIFileStatus.RcvAccepted))) { - withApi { receiveFile(rhId, r.user, file.fileId, encrypted = cItem.encryptLocalFile && chatController.appPrefs.privacyEncryptLocalFiles.get(), auto = true) } + withBGApi { receiveFile(rhId, r.user, file.fileId, encrypted = cItem.encryptLocalFile && chatController.appPrefs + .privacyEncryptLocalFiles.get(), auto = true) } } if (cItem.showNotification && (allowedToShowNotification() || chatModel.chatId.value != cInfo.id || chatModel.remoteHostId() != rhId)) { ntfManager.notifyMessageReceived(r.user, cInfo, cItem) @@ -1863,10 +1866,8 @@ object ChatController { } withCall(r, r.contact) { _ -> chatModel.callCommand.add(WCallCommand.End) - withApi { - chatModel.activeCall.value = null - chatModel.showCallView.value = false - } + chatModel.activeCall.value = null + chatModel.showCallView.value = false } } is CR.ContactSwitch -> @@ -1977,7 +1978,7 @@ object ChatController { r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.CRITICAL -> { chatModel.processedCriticalError.newError(r.chatError.agentError, r.chatError.agentError.offerRestart) } - r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.INTERNAL && appPrefs.showInternalErrors.get() -> { + r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.INTERNAL && appPrefs.developerTools.get() && appPrefs.showInternalErrors.get() -> { chatModel.processedInternalError.newError(r.chatError.agentError, false) } } @@ -2004,20 +2005,18 @@ object ChatController { } } - fun switchToLocalSession() { + suspend fun switchToLocalSession() { val m = chatModel m.remoteCtrlSession.value = null - withBGApi { - val users = listUsers(null) - m.users.clear() - m.users.addAll(users) - getUserChatData(null) - val statuses = apiGetNetworkStatuses(null) - if (statuses != null) { - chatModel.networkStatuses.clear() - val ss = statuses.associate { it.agentConnId to it.networkStatus }.toMap() - chatModel.networkStatuses.putAll(ss) - } + val users = listUsers(null) + m.users.clear() + m.users.addAll(users) + getUserChatData(null) + val statuses = apiGetNetworkStatuses(null) + if (statuses != null) { + chatModel.networkStatuses.clear() + val ss = statuses.associate { it.agentConnId to it.networkStatus }.toMap() + chatModel.networkStatuses.putAll(ss) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 8ed662ae6..ec81e5441 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -42,7 +42,7 @@ val appPreferences: AppPreferences val chatController: ChatController = ChatController fun initChatControllerAndRunMigrations() { - withBGApi { + withLongRunningApi(slow = 30_000, deadlock = 60_000) { if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) { initChatController(startChat = ::showStartChatAfterRestartAlert) } else { @@ -57,7 +57,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat if (chatModel.ctrlInitInProgress.value) return chatModel.ctrlInitInProgress.value = true val dbKey = useKey ?: DatabaseUtils.useDatabaseKey() - val confirm = confirmMigrations ?: if (appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp + val confirm = confirmMigrations ?: if (appPreferences.developerTools.get() && appPreferences.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp val migrated: Array = chatMigrateInit(dbAbsolutePrefixPath, dbKey, confirm.value) val res: DBMigrationResult = kotlin.runCatching { json.decodeFromString(migrated[0] as String) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index d3d30b58c..bcdf2e838 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -50,7 +50,7 @@ private fun sendCommand(chatModel: ChatModel, composeState: MutableState Unit) { } fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { - withApi { - val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null)) ?: return@withApi + withBGApi { + val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null)) ?: return@withBGApi controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress) chatModel.chatRunning.value = false controller.startChat(user) @@ -186,11 +186,11 @@ fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) { } fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () -> Unit) { - withApi { + withBGApi { val rhId = chatModel.remoteHostId() val user = chatModel.controller.apiCreateActiveUser( rhId, Profile(displayName.trim(), "", null) - ) ?: return@withApi + ) ?: return@withBGApi chatModel.currentUser.value = user if (chatModel.users.isEmpty()) { chatModel.controller.startChat(user) @@ -206,10 +206,10 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () } fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () -> Unit) { - withApi { + withBGApi { chatModel.currentUser.value = chatModel.controller.apiCreateActiveUser( null, Profile(displayName.trim(), "", null) - ) ?: return@withApi + ) ?: return@withBGApi val onboardingStage = chatModel.controller.appPrefs.onboardingStage if (chatModel.users.isEmpty()) { onboardingStage.set(if (appPlatform.isDesktop && chatModel.controller.appPrefs.initialRandomDBPassphrase.get() && !chatModel.desktopOnboardingRandomPassword.value) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt index 139c749e5..b215badf2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt @@ -2,7 +2,7 @@ package chat.simplex.common.views.call import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* -import chat.simplex.common.views.helpers.withApi +import chat.simplex.common.views.helpers.withBGApi import kotlinx.datetime.Clock import kotlin.time.Duration.Companion.minutes @@ -28,13 +28,13 @@ class CallManager(val chatModel: ChatModel) { if (call == null) { justAcceptIncomingCall(invitation = invitation) } else { - withApi { + withBGApi { chatModel.switchingCall.value = true try { endCall(call = call) justAcceptIncomingCall(invitation = invitation) } finally { - withApi { chatModel.switchingCall.value = false } + chatModel.switchingCall.value = false } } } @@ -90,7 +90,7 @@ class CallManager(val chatModel: ChatModel) { activeCallInvitation.value = null ntfManager.cancelCallNotification() } - withApi { + withBGApi { if (!controller.apiRejectCall(invitation.remoteHostId, invitation.contact)) { Log.e(TAG, "apiRejectCall error") } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index d87fce5fb..32f6d6a6d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -69,11 +69,9 @@ fun ChatInfoView( currentUser, sendReceipts = sendReceipts, setSendReceipts = { sendRcpts -> - withApi { - val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(sendRcpts = sendRcpts.bool) - updateChatSettings(chat, chatSettings, chatModel) - sendReceipts.value = sendRcpts - } + val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(sendRcpts = sendRcpts.bool) + updateChatSettings(chat, chatSettings, chatModel) + sendReceipts.value = sendRcpts }, connStats = connStats, contactNetworkStatus.value, @@ -96,7 +94,7 @@ fun ChatInfoView( clearChat = { clearChatDialog(chat, chatModel, close) }, switchContactAddress = { showSwitchAddressAlert(switchAddress = { - withApi { + withBGApi { val cStats = chatModel.controller.apiSwitchContact(chatRh, contact.contactId) connStats.value = cStats if (cStats != null) { @@ -108,7 +106,7 @@ fun ChatInfoView( }, abortSwitchContactAddress = { showAbortSwitchAddressAlert(abortSwitchAddress = { - withApi { + withBGApi { val cStats = chatModel.controller.apiAbortSwitchContact(chatRh, contact.contactId) connStats.value = cStats if (cStats != null) { @@ -118,7 +116,7 @@ fun ChatInfoView( }) }, syncContactConnection = { - withApi { + withBGApi { val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false) connStats.value = cStats if (cStats != null) { @@ -129,7 +127,7 @@ fun ChatInfoView( }, syncContactConnectionForce = { showSyncConnectionForceAlert(syncConnectionForce = { - withApi { + withBGApi { val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = true) connStats.value = cStats if (cStats != null) { @@ -208,18 +206,14 @@ fun deleteContactDialog(chat: Chat, chatModel: ChatModel, close: (() -> Unit)? = // Delete and notify contact SectionItemView({ AlertManager.shared.hideAlert() - withApi { - deleteContact(chat, chatModel, close, notify = true) - } + deleteContact(chat, chatModel, close, notify = true) }) { Text(generalGetString(MR.strings.delete_and_notify_contact), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) } // Delete SectionItemView({ AlertManager.shared.hideAlert() - withApi { - deleteContact(chat, chatModel, close, notify = false) - } + deleteContact(chat, chatModel, close, notify = false) }) { Text(generalGetString(MR.strings.delete_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) } @@ -227,9 +221,7 @@ fun deleteContactDialog(chat: Chat, chatModel: ChatModel, close: (() -> Unit)? = // Delete SectionItemView({ AlertManager.shared.hideAlert() - withApi { - deleteContact(chat, chatModel, close) - } + deleteContact(chat, chatModel, close) }) { Text(generalGetString(MR.strings.delete_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error) } @@ -247,7 +239,7 @@ fun deleteContactDialog(chat: Chat, chatModel: ChatModel, close: (() -> Unit)? = fun deleteContact(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?, notify: Boolean? = null) { val chatInfo = chat.chatInfo - withApi { + withBGApi { val chatRh = chat.remoteHostId val r = chatModel.controller.apiDeleteChat(chatRh, chatInfo.chatType, chatInfo.apiId, notify) if (r) { @@ -269,7 +261,7 @@ fun clearChatDialog(chat: Chat, chatModel: ChatModel, close: (() -> Unit)? = nul text = generalGetString(MR.strings.clear_chat_warning), confirmText = generalGetString(MR.strings.clear_verb), onConfirm = { - withApi { + withBGApi { val chatRh = chat.remoteHostId val updatedChatInfo = chatModel.controller.apiClearChat(chatRh, chatInfo.chatType, chatInfo.apiId) if (updatedChatInfo != null) { @@ -676,7 +668,7 @@ fun ShareAddressButton(onClick: () -> Unit) { ) } -private fun setContactAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withApi { +private fun setContactAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withBGApi { val chatRh = chat.remoteHostId chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let { chatModel.updateContact(chatRh, it) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 38c8112e4..b197c4ada 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -164,7 +164,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: return@ChatLayout } hideKeyboard(view) - withApi { + withBGApi { // The idea is to preload information before showing a modal because large groups can take time to load all members var preloadedContactInfo: Pair? = null var preloadedCode: String? = null @@ -205,7 +205,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: }, showMemberInfo = { groupInfo: GroupInfo, member: GroupMember -> hideKeyboard(view) - withApi { + withBGApi { val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) val stats = r?.second val (_, code) = if (member.memberActive) { @@ -228,7 +228,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) val firstId = chatModel.chatItems.firstOrNull()?.id if (c != null && firstId != null) { - withApi { + withBGApi { Log.d(TAG, "TODOCHAT: loadPrevMessages: loading for ${c.id}, current chatId ${ChatModel.chatId.value}, size was ${ChatModel.chatItems.size}") apiLoadPrevMessages(c, chatModel, firstId, searchText.value) Log.d(TAG, "TODOCHAT: loadPrevMessages: loaded for ${c.id}, current chatId ${ChatModel.chatId.value}, size now ${ChatModel.chatItems.size}") @@ -236,7 +236,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } }, deleteMessage = { itemId, mode -> - withApi { + withBGApi { val cInfo = chat.chatInfo val toDeleteItem = chatModel.chatItems.firstOrNull { it.id == itemId } val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo) @@ -291,13 +291,13 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } }, receiveFile = { fileId, encrypted -> - withApi { chatModel.controller.receiveFile(chatRh, user, fileId, encrypted) } + withBGApi { chatModel.controller.receiveFile(chatRh, user, fileId, encrypted) } }, cancelFile = { fileId -> - withApi { chatModel.controller.cancelFile(chatRh, user, fileId) } + withBGApi { chatModel.controller.cancelFile(chatRh, user, fileId) } }, joinGroup = { groupId, onComplete -> - withApi { + withBGApi { chatModel.controller.apiJoinGroup(chatRh, groupId) onComplete.invoke() } @@ -314,11 +314,11 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: }, endCall = { val call = chatModel.activeCall.value - if (call != null) withApi { chatModel.callManager.endCall(call) } + if (call != null) withBGApi { chatModel.callManager.endCall(call) } }, acceptCall = { contact -> hideKeyboard(view) - withApi { + withBGApi { val invitation = chatModel.callInvitations.remove(contact.id) ?: controller.apiGetCallInvitations(chatModel.remoteHostId()).firstOrNull { it.contact.id == contact.id } if (invitation == null) { @@ -329,17 +329,17 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } }, acceptFeature = { contact, feature, param -> - withApi { + withBGApi { chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param) } }, openDirectChat = { contactId -> - withApi { + withBGApi { openDirectChat(chatRh, contactId, chatModel) } }, updateContactStats = { contact -> - withApi { + withBGApi { val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) if (r != null) { val contactStats = r.first @@ -349,7 +349,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } }, updateMemberStats = { groupInfo, member -> - withApi { + withBGApi { val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) if (r != null) { val memStats = r.second @@ -360,7 +360,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } }, syncContactConnection = { contact -> - withApi { + withBGApi { val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false) if (cStats != null) { chatModel.updateContactConnectionStats(chatRh, contact, cStats) @@ -368,7 +368,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } }, syncMemberConnection = { groupInfo, member -> - withApi { + withBGApi { val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false) if (r != null) { chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second) @@ -382,7 +382,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: chatModel.groupMembers.find { it.id == memberId } }, setReaction = { cInfo, cItem, add, reaction -> - withApi { + withBGApi { val updatedCI = chatModel.controller.apiChatItemReaction( rh = chatRh, type = cInfo.chatType, @@ -397,7 +397,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } }, showItemDetails = { cInfo, cItem -> - withApi { + withBGApi { val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id) if (ciInfo != null) { if (chat.chatInfo is ChatInfo.Group) { @@ -416,7 +416,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: }, addMembers = { groupInfo -> hideKeyboard(view) - withApi { + withBGApi { setGroupMembers(chatRh, groupInfo, chatModel) ModalManager.end.closeModals() ModalManager.end.showModalCloseable(true) { close -> @@ -426,7 +426,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: }, openGroupLink = { groupInfo -> hideKeyboard(view) - withApi { + withBGApi { val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId) ModalManager.end.closeModals() ModalManager.end.showModalCloseable(true) { @@ -451,7 +451,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: if (searchText.value == value) return@ChatLayout if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout - withApi { + withBGApi { apiFindMessages(c, chatModel, value) searchText.value = value } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index b230d261f..30786fae9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -267,7 +267,7 @@ fun ComposeView( fun loadLinkPreview(url: String, wait: Long? = null) { if (pendingLinkUrl.value == url) { composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(null)) - withApi { + withBGApi { if (wait != null) delay(wait) val lp = getLinkPreview(url) if (lp != null && pendingLinkUrl.value == url) { @@ -575,7 +575,7 @@ fun ComposeView( fun allowVoiceToContact() { val contact = (chat.chatInfo as ChatInfo.Direct?)?.contact ?: return - withApi { + withBGApi { chatModel.controller.allowFeatureToContact(chat.remoteHostId, contact, ChatFeature.Voice) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt index c12982ada..f412c0215 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt @@ -35,7 +35,7 @@ fun ContactPreferencesView( var currentFeaturesAllowed by rememberSaveable(ct, stateSaver = serializableSaver()) { mutableStateOf(featuresAllowed) } fun savePrefs(afterSave: () -> Unit = {}) { - withApi { + withBGApi { val prefs = contactFeaturesAllowedToPrefs(featuresAllowed) val toContact = m.controller.apiSetContactPrefs(rhId, ct.contactId, prefs) if (toContact != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt index 37ee9729f..56656b7fc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt @@ -54,7 +54,7 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea }, inviteMembers = { allowModifyMembers = false - withApi { + withBGApi { for (contactId in selectedContacts) { val member = chatModel.controller.apiAddMember(rhId, groupInfo.groupId, contactId, selectedRole.value) if (member != null) { @@ -68,7 +68,7 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea }, clearSelection = { selectedContacts.clear() }, addContact = { contactId -> if (contactId !in selectedContacts) selectedContacts.add(contactId) }, - removeContact = { contactId -> selectedContacts.removeIf { it == contactId } }, + removeContact = { contactId -> selectedContacts.removeAll { it == contactId } }, close = close, ) KeyChangeEffect(chatModel.chatId.value) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index d3b1841fe..c2fc1de41 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -56,11 +56,9 @@ fun GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLi currentUser, sendReceipts = sendReceipts, setSendReceipts = { sendRcpts -> - withApi { - val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(sendRcpts = sendRcpts.bool) - updateChatSettings(chat, chatSettings, chatModel) - sendReceipts.value = sendRcpts - } + val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(sendRcpts = sendRcpts.bool) + updateChatSettings(chat, chatSettings, chatModel) + sendReceipts.value = sendRcpts }, members = chatModel.groupMembers .filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved } @@ -68,7 +66,7 @@ fun GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLi developerTools, groupLink, addMembers = { - withApi { + withBGApi { setGroupMembers(rhId, groupInfo, chatModel) ModalManager.end.showModalCloseable(true) { close -> AddGroupMembersView(rhId, groupInfo, false, chatModel, close) @@ -76,7 +74,7 @@ fun GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLi } }, showMemberInfo = { member -> - withApi { + withBGApi { val r = chatModel.controller.apiGroupMemberInfo(rhId, groupInfo.groupId, member.groupMemberId) val stats = r?.second val (_, code) = if (member.memberActive) { @@ -131,7 +129,7 @@ fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, cl text = generalGetString(alertTextKey), confirmText = generalGetString(MR.strings.delete_verb), onConfirm = { - withApi { + withBGApi { val r = chatModel.controller.apiDeleteChat(chat.remoteHostId, chatInfo.chatType, chatInfo.apiId) if (r) { chatModel.removeChat(chat.remoteHostId, chatInfo.id) @@ -154,7 +152,7 @@ fun leaveGroupDialog(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl text = generalGetString(MR.strings.you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved), confirmText = generalGetString(MR.strings.leave_group_button), onConfirm = { - withApi { + withBGApi { chatModel.controller.leaveGroup(rhId, groupInfo.groupId) close?.invoke() } @@ -169,7 +167,7 @@ private fun removeMemberAlert(rhId: Long?, groupInfo: GroupInfo, mem: GroupMembe text = generalGetString(MR.strings.member_will_be_removed_from_group_cannot_be_undone), confirmText = generalGetString(MR.strings.remove_member_confirmation), onConfirm = { - withApi { + withBGApi { val updatedMember = chatModel.controller.apiRemoveMember(rhId, groupInfo.groupId, mem.groupMemberId) if (updatedMember != null) { chatModel.upsertGroupMember(rhId, groupInfo, updatedMember) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt index dfb679c08..7b198bc41 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt @@ -38,7 +38,7 @@ fun GroupLinkView( var creatingLink by rememberSaveable { mutableStateOf(false) } fun createLink() { creatingLink = true - withApi { + withBGApi { val link = chatModel.controller.apiCreateGroupLink(rhId, groupInfo.groupId) if (link != null) { groupLink = link.first @@ -78,7 +78,7 @@ fun GroupLinkView( text = generalGetString(MR.strings.all_group_members_will_remain_connected), confirmText = generalGetString(MR.strings.delete_verb), onConfirm = { - withApi { + withBGApi { val r = chatModel.controller.apiDeleteGroupLink(rhId, groupInfo.groupId) if (r) { groupLink = null diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index bab7bb9be..84d7afaae 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -66,7 +66,7 @@ fun GroupMemberInfoView( connectionCode, getContactChat = { chatModel.getContactChat(it) }, openDirectChat = { - withApi { + withBGApi { val c = chatModel.controller.apiGetChat(rhId, ChatType.Direct, it) if (c != null) { if (chatModel.getContactChat(it) == null) { @@ -81,7 +81,7 @@ fun GroupMemberInfoView( } }, createMemberContact = { - withApi { + withBGApi { progressIndicator = true val memberContact = chatModel.controller.apiCreateMemberContact(rhId, groupInfo.apiId, member.groupMemberId) if (memberContact != null) { @@ -107,7 +107,7 @@ fun GroupMemberInfoView( updateMemberRoleDialog(it, member, onDismiss = { newRole.value = prevValue }) { - withApi { + withBGApi { kotlin.runCatching { val mem = chatModel.controller.apiMemberRole(rhId, groupInfo.groupId, member.groupMemberId, it) chatModel.upsertGroupMember(rhId, groupInfo, mem) @@ -119,7 +119,7 @@ fun GroupMemberInfoView( }, switchMemberAddress = { showSwitchAddressAlert(switchAddress = { - withApi { + withBGApi { val r = chatModel.controller.apiSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId) if (r != null) { connStats.value = r.second @@ -131,7 +131,7 @@ fun GroupMemberInfoView( }, abortSwitchMemberAddress = { showAbortSwitchAddressAlert(abortSwitchAddress = { - withApi { + withBGApi { val r = chatModel.controller.apiAbortSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId) if (r != null) { connStats.value = r.second @@ -142,7 +142,7 @@ fun GroupMemberInfoView( }) }, syncMemberConnection = { - withApi { + withBGApi { val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = false) if (r != null) { connStats.value = r.second @@ -153,7 +153,7 @@ fun GroupMemberInfoView( }, syncMemberConnectionForce = { showSyncConnectionForceAlert(syncConnectionForce = { - withApi { + withBGApi { val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = true) if (r != null) { connStats.value = r.second @@ -204,7 +204,7 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c text = generalGetString(MR.strings.member_will_be_removed_from_group_cannot_be_undone), confirmText = generalGetString(MR.strings.remove_member_confirmation), onConfirm = { - withApi { + withBGApi { val removedMember = chatModel.controller.apiRemoveMember(rhId, member.groupId, member.groupMemberId) if (removedMember != null) { chatModel.upsertGroupMember(rhId, groupInfo, removedMember) @@ -505,7 +505,7 @@ private fun updateMemberRoleDialog( fun connectViaMemberAddressAlert(rhId: Long?, connReqUri: String) { try { val uri = URI(connReqUri) - withApi { + withBGApi { planAndConnect(rhId, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() }) } } catch (e: RuntimeException) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index bd584f0c8..694dac889 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -32,7 +32,7 @@ fun GroupPreferencesView(m: ChatModel, rhId: Long?, chatId: String, close: () -> var currentPreferences by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(preferences) } fun savePrefs(afterSave: () -> Unit = {}) { - withApi { + withBGApi { val gp = gInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences()) val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp) if (g != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt index f92fd88dc..35bec31d7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt @@ -35,7 +35,7 @@ fun GroupProfileView(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl close = close, groupProfile = groupInfo.groupProfile, saveProfile = { p -> - withApi { + withBGApi { val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, p) if (gInfo != null) { chatModel.updateGroup(rhId, gInfo) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt index c50a80c4e..9124eed4c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt @@ -36,7 +36,7 @@ fun GroupWelcomeView(m: ChatModel, rhId: Long?, groupInfo: GroupInfo, close: () val welcomeText = remember { mutableStateOf(gInfo.groupProfile.description ?: "") } fun save(afterSave: () -> Unit = {}) { - withApi { + withBGApi { var welcome: String? = welcomeText.value.trim('\n', ' ') if (welcome?.length == 0) { welcome = null diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt index 068dd362b..074969fbf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt @@ -101,7 +101,7 @@ fun CIFileView( filePath = getLoadedFilePath(file) } if (filePath != null) { - withApi { + withBGApi { saveFileLauncher.launch(file.fileName) } } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 8cff56342..a80c70398 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -394,7 +394,7 @@ fun JoinGroupAction( inProgress: MutableState ) { val joinGroup: () -> Unit = { - withApi { + withBGApi { inProgress.value = true chatModel.controller.apiJoinGroup(chat.remoteHostId, groupInfo.groupId) inProgress.value = false @@ -581,7 +581,7 @@ fun contactRequestAlertDialog(rhId: Long?, contactRequest: ChatInfo.ContactReque } fun acceptContactRequest(rhId: Long?, incognito: Boolean, apiId: Long, contactRequest: ChatInfo.ContactRequest?, isCurrentUser: Boolean, chatModel: ChatModel) { - withApi { + withBGApi { val contact = chatModel.controller.apiAcceptContactRequest(rhId, incognito, apiId) if (contact != null && isCurrentUser && contactRequest != null) { val chat = Chat(remoteHostId = rhId, ChatInfo.Direct(contact), listOf()) @@ -591,7 +591,7 @@ fun acceptContactRequest(rhId: Long?, incognito: Boolean, apiId: Long, contactRe } fun rejectContactRequest(rhId: Long?, contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) { - withApi { + withBGApi { chatModel.controller.apiRejectContactRequest(rhId, contactRequest.apiId) chatModel.removeChat(rhId, contactRequest.id) } @@ -606,7 +606,7 @@ fun deleteContactConnectionAlert(rhId: Long?, connection: PendingContactConnecti ), confirmText = generalGetString(MR.strings.delete_verb), onConfirm = { - withApi { + withBGApi { AlertManager.shared.hideAlert() if (chatModel.controller.apiDeleteChat(rhId, ChatType.ContactConnection, connection.apiId)) { chatModel.removeChat(rhId, connection.id) @@ -625,7 +625,7 @@ fun pendingContactAlertDialog(rhId: Long?, chatInfo: ChatInfo, chatModel: ChatMo text = generalGetString(MR.strings.alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry), confirmText = generalGetString(MR.strings.button_delete_contact), onConfirm = { - withApi { + withBGApi { val r = chatModel.controller.apiDeleteChat(rhId, chatInfo.chatType, chatInfo.apiId) if (r) { chatModel.removeChat(rhId, chatInfo.id) @@ -654,7 +654,7 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress( Column { SectionItemView({ AlertManager.privacySensitive.hideAlert() - withApi { + withBGApi { close?.invoke() val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = false) if (ok && openChat) { @@ -666,7 +666,7 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress( } SectionItemView({ AlertManager.privacySensitive.hideAlert() - withApi { + withBGApi { close?.invoke() val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = true) if (ok && openChat) { @@ -707,7 +707,7 @@ fun acceptGroupInvitationAlertDialog(rhId: Long?, groupInfo: GroupInfo, chatMode text = generalGetString(MR.strings.you_are_invited_to_group_join_to_connect_with_group_members), confirmText = if (groupInfo.membership.memberIncognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), onConfirm = { - withApi { + withBGApi { inProgress?.value = true chatModel.controller.apiJoinGroup(rhId, groupInfo.groupId) inProgress?.value = false @@ -728,7 +728,7 @@ fun cantInviteIncognitoAlert() { } fun deleteGroup(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) { - withApi { + withBGApi { val r = chatModel.controller.apiDeleteChat(rhId, ChatType.Group, groupInfo.apiId) if (r) { chatModel.removeChat(rhId, groupInfo.id) @@ -769,7 +769,7 @@ fun updateChatSettings(chat: Chat, chatSettings: ChatSettings, chatModel: ChatMo } else -> null } - withApi { + withBGApi { val res = when (newChatInfo) { is ChatInfo.Direct -> with(newChatInfo) { chatModel.controller.apiSetSettings(chat.remoteHostId, chatType, apiId, contact.chatSettings) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 0d8285f6f..6570db469 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -319,7 +319,7 @@ fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) { if (chatModel.currentUser.value == null) { chatModel.appOpenUrl.value = rhId to uri } else { - withApi { + withBGApi { planAndConnect(rhId, uri, incognito = null, close = null) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 0400a5e33..8800682f4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -31,8 +31,8 @@ import chat.simplex.common.views.remote.* import chat.simplex.common.views.usersettings.doWithAuth import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.* import kotlinx.coroutines.flow.* -import kotlinx.coroutines.launch import kotlin.math.roundToInt @Composable @@ -117,7 +117,9 @@ fun UserPicker( LaunchedEffect(Unit) { // Controller.ctrl can be null when self-destructing activates if (controller.ctrl != null && controller.ctrl != -1L) { - controller.reloadRemoteHosts() + withBGApi { + controller.reloadRemoteHosts() + } } } val UsersView: @Composable ColumnScope.() -> Unit = { @@ -125,7 +127,7 @@ fun UserPicker( UserProfilePickerItem(u.user, u.unreadCount, openSettings = settingsClicked) { userPickerState.value = AnimatedViewState.HIDING if (!u.user.activeUser) { - scope.launch { + withBGApi { controller.showProgressIfNeeded { ModalManager.closeAllModalsEverywhere() chatModel.controller.changeActiveUser(u.user.remoteHostId, u.user.userId, null) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt index 8738db2df..45b4381d4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt @@ -34,7 +34,7 @@ fun ChatArchiveView(m: ChatModel, title: String, archiveName: String, archiveTim ChatArchiveLayout( title, archiveTime, - saveArchive = { withApi { saveArchiveLauncher.launch(archivePath.substringAfterLast(File.separator)) }}, + saveArchive = { withBGApi { saveArchiveLauncher.launch(archivePath.substringAfterLast(File.separator)) }}, deleteArchiveAlert = { deleteArchiveAlert(m, archivePath) } ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt index e34f80a7e..406f79d45 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt @@ -62,7 +62,7 @@ fun DatabaseEncryptionView(m: ChatModel) { initialRandomDBPassphrase, progressIndicator, onConfirmEncrypt = { - withApi { + withBGApi { encryptDatabase(currentKey, newKey, confirmNewKey, initialRandomDBPassphrase, useKeychain, storedKey, progressIndicator) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 2db8a1595..8b22eba09 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -27,6 +27,7 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.common.platform.* import chat.simplex.res.MR +import kotlinx.coroutines.delay import kotlinx.coroutines.sync.withLock import kotlinx.datetime.* import java.io.* @@ -88,8 +89,8 @@ fun DatabaseView( chatItemTTL, user, m.users, - startChat = { startChat(m, chatLastStart, m.chatDbChanged) }, - stopChatAlert = { stopChatAlert(m) }, + startChat = { startChat(m, chatLastStart, m.chatDbChanged, progressIndicator) }, + stopChatAlert = { stopChatAlert(m, progressIndicator) }, exportArchive = { exportArchive(m, progressIndicator, chatArchiveName, chatArchiveTime, chatArchiveFile, saveArchiveLauncher) }, deleteChatAlert = { deleteChatAlert(m, progressIndicator) }, deleteAppFilesAndMedia = { deleteFilesAndMediaAlert(appFilesCountAndSize) }, @@ -187,7 +188,7 @@ fun DatabaseLayout( Text(generalGetString(MR.strings.disconnect_remote_hosts), Modifier.fillMaxWidth(), color = WarningOrange) } } - RunChatSetting(runChat, stopped, toggleEnabled, startChat, stopChatAlert) + RunChatSetting(runChat, stopped, toggleEnabled && !progressIndicator, startChat, stopChatAlert) } SectionTextFooter( if (stopped) { @@ -239,7 +240,7 @@ fun DatabaseLayout( SettingsActionItem( painterResource(MR.images.ic_download), stringResource(MR.strings.import_database), - { withApi { importArchiveLauncher.launch("application/zip") } }, + { withBGApi { importArchiveLauncher.launch("application/zip") } }, textColor = Color.Red, iconColor = Color.Red, disabled = operationsDisabled @@ -366,9 +367,10 @@ fun chatArchiveTitle(chatArchiveTime: Instant, chatLastStart: Instant): String { return stringResource(if (chatArchiveTime < chatLastStart) MR.strings.old_database_archive else MR.strings.new_database_archive) } -fun startChat(m: ChatModel, chatLastStart: MutableState, chatDbChanged: MutableState) { - withApi { +fun startChat(m: ChatModel, chatLastStart: MutableState, chatDbChanged: MutableState, progressIndicator: MutableState? = null) { + withBGApi { try { + progressIndicator?.value = true if (chatDbChanged.value) { initChatController() chatDbChanged.value = false @@ -376,12 +378,12 @@ fun startChat(m: ChatModel, chatLastStart: MutableState, chatDbChanged if (m.chatDbStatus.value !is DBMigrationResult.OK) { /** Hide current view and show [DatabaseErrorView] */ ModalManager.closeAllModalsEverywhere() - return@withApi + return@withBGApi } val user = m.currentUser.value if (user == null) { ModalManager.closeAllModalsEverywhere() - return@withApi + return@withBGApi } else { m.controller.startChat(user) } @@ -392,16 +394,18 @@ fun startChat(m: ChatModel, chatLastStart: MutableState, chatDbChanged } catch (e: Error) { m.chatRunning.value = false AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_starting_chat), e.toString()) + } finally { + progressIndicator?.value = false } } } -private fun stopChatAlert(m: ChatModel) { +private fun stopChatAlert(m: ChatModel, progressIndicator: MutableState? = null) { AlertManager.shared.showAlertDialog( title = generalGetString(MR.strings.stop_chat_question), text = generalGetString(MR.strings.stop_chat_to_export_import_or_delete_chat_database), confirmText = generalGetString(MR.strings.stop_chat_confirmation), - onConfirm = { authStopChat(m) }, + onConfirm = { authStopChat(m, progressIndicator = progressIndicator) }, onDismiss = { m.chatRunning.value = true } ) } @@ -415,7 +419,7 @@ private fun exportProhibitedAlert() { ) } -fun authStopChat(m: ChatModel, onStop: (() -> Unit)? = null) { +fun authStopChat(m: ChatModel, progressIndicator: MutableState? = null, onStop: (() -> Unit)? = null) { if (m.controller.appPrefs.performLA.get()) { authenticate( generalGetString(MR.strings.auth_stop_chat), @@ -423,7 +427,7 @@ fun authStopChat(m: ChatModel, onStop: (() -> Unit)? = null) { completed = { laResult -> when (laResult) { LAResult.Success, is LAResult.Unavailable -> { - stopChat(m, onStop) + stopChat(m, progressIndicator, onStop) } is LAResult.Error -> { m.chatRunning.value = true @@ -436,19 +440,22 @@ fun authStopChat(m: ChatModel, onStop: (() -> Unit)? = null) { } ) } else { - stopChat(m, onStop) + stopChat(m, progressIndicator, onStop) } } -private fun stopChat(m: ChatModel, onStop: (() -> Unit)? = null) { - withApi { +private fun stopChat(m: ChatModel, progressIndicator: MutableState? = null, onStop: (() -> Unit)? = null) { + withBGApi { try { + progressIndicator?.value = true stopChatAsync(m) platform.androidChatStopped() onStop?.invoke() } catch (e: Error) { m.chatRunning.value = true AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_stopping_chat), e.toString()) + } finally { + progressIndicator?.value = false } } } @@ -493,7 +500,7 @@ private fun exportArchive( saveArchiveLauncher: FileChooserLauncher ) { progressIndicator.value = true - withApi { + withBGApi { try { val archiveFile = exportChatArchive(m, chatArchiveName, chatArchiveTime, chatArchiveFile) chatArchiveFile.value = archiveFile @@ -567,7 +574,7 @@ private fun importArchive( progressIndicator.value = true val archivePath = saveArchiveFromURI(importedArchiveURI) if (archivePath != null) { - withApi { + withBGApi { try { m.controller.apiDeleteStorage() try { @@ -635,7 +642,7 @@ private fun deleteChatAlert(m: ChatModel, progressIndicator: MutableState) { progressIndicator.value = true - withApi { + withBGApi { try { deleteChatAsync(m) operationEnded(m, progressIndicator) { @@ -658,7 +665,7 @@ private fun setCiTTL( ) { Log.d(TAG, "DatabaseView setChatItemTTL ${chatItemTTL.value.seconds ?: -1}") progressIndicator.value = true - withApi { + withBGApi { try { m.controller.setChatItemTTL(rhId, chatItemTTL.value) // Update model on success diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt index 3ab74a6ad..52dc2c065 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt @@ -89,7 +89,7 @@ enum class MigrationConfirmation(val value: String) { } fun defaultMigrationConfirmation(appPrefs: AppPreferences): MigrationConfirmation = - if (appPrefs.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp + if (appPrefs.developerTools.get() && appPrefs.confirmDBUpgrades.get()) MigrationConfirmation.Error else MigrationConfirmation.YesUp @Serializable sealed class MigrationError { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index 2e7f401bf..f64bd321f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -45,7 +45,7 @@ class ModalData { } class ModalManager(private val placement: ModalPlacement? = null) { - private val modalViews = arrayListOf Unit) -> Unit)>>() + private val modalViews = arrayListOf Unit) -> Unit)>>() private val modalCount = mutableStateOf(0) private val toRemove = mutableSetOf() private var oldViewChanging = AtomicBoolean(false) @@ -65,8 +65,9 @@ class ModalManager(private val placement: ModalPlacement? = null) { } } - fun showCustomModal(animated: Boolean = true, modal: @Composable (close: () -> Unit) -> Unit) { + fun showCustomModal(animated: Boolean = true, modal: @Composable ModalData.(close: () -> Unit) -> Unit) { Log.d(TAG, "ModalManager.showCustomModal") + val data = ModalData() // Means, animation is in progress or not started yet. Do not wait until animation finishes, just remove all from screen. // This is useful when invoking close() and ShowCustomModal one after another without delay. Otherwise, screen will hold prev view if (toRemove.isNotEmpty()) { @@ -75,7 +76,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { // Make animated appearance only on Android (everytime) and on Desktop (when it's on the start part of the screen or modals > 0) // to prevent unneeded animation on different situations val anim = if (appPlatform.isAndroid) animated else animated && (modalCount.value > 0 || placement == ModalPlacement.START) - modalViews.add(anim to modal) + modalViews.add(Triple(anim, data, modal)) modalCount.value = modalViews.size - toRemove.size if (placement == ModalPlacement.CENTER) { @@ -117,7 +118,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { fun showInView() { // Without animation if (modalCount.value > 0 && modalViews.lastOrNull()?.first == false) { - modalViews.lastOrNull()?.second?.invoke(::closeModal) + modalViews.lastOrNull()?.let { it.third(it.second, ::closeModal) } return } AnimatedContent(targetState = modalCount.value, @@ -129,7 +130,7 @@ class ModalManager(private val placement: ModalPlacement? = null) { }.using(SizeTransform(clip = false)) } ) { - modalViews.getOrNull(it - 1)?.second?.invoke(::closeModal) + modalViews.getOrNull(it - 1)?.let { it.third(it.second, ::closeModal) } // This is needed because if we delete from modalViews immediately on request, animation will be bad if (toRemove.isNotEmpty() && it == modalCount.value && transition.currentState == EnterExitState.Visible && !transition.isRunning) { runAtomically { toRemove.removeIf { elem -> modalViews.removeAt(elem); true } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt index 4b44777a3..d98f38d71 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt @@ -43,9 +43,8 @@ class ProcessedErrors (val interval: Long) { title = title, text = text, confirmText = generalGetString(MR.strings.restart_chat_button), - onConfirm = { - withApi { restartChatOrApp() } - }) + onConfirm = ::restartChatOrApp + ) } else { AlertManager.shared.showAlertMsg( title = title, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index cae9523e1..fbb06f3e1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -2,6 +2,7 @@ package chat.simplex.common.views.helpers import androidx.compose.runtime.* import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.* import androidx.compose.ui.text.* @@ -21,15 +22,71 @@ import java.net.URI import java.nio.file.Files import java.text.SimpleDateFormat import java.util.* +import java.util.concurrent.Executors import kotlin.math.* +private val singleThreadDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + fun withApi(action: suspend CoroutineScope.() -> Unit): Job = withScope(GlobalScope, action) fun withScope(scope: CoroutineScope, action: suspend CoroutineScope.() -> Unit): Job = - scope.launch { withContext(Dispatchers.Main, action) } + Exception().let { + scope.launch { withContext(Dispatchers.Main, block = { wrapWithLogging(action, it) }) } + } fun withBGApi(action: suspend CoroutineScope.() -> Unit): Job = - CoroutineScope(Dispatchers.Default).launch(block = action) + Exception().let { + CoroutineScope(singleThreadDispatcher).launch(block = { wrapWithLogging(action, it) }) + } + +fun withLongRunningApi(slow: Long = 120_000, deadlock: Long = 240_000, action: suspend CoroutineScope.() -> Unit): Job = + Exception().let { + CoroutineScope(Dispatchers.Default).launch(block = { wrapWithLogging(action, it, slow = slow, deadlock = deadlock) }) + } + +private suspend fun wrapWithLogging(action: suspend CoroutineScope.() -> Unit, exception: java.lang.Exception, slow: Long = 10_000, deadlock: Long = 60_000) = coroutineScope { + val start = System.currentTimeMillis() + val job = launch { + delay(deadlock) + Log.e(TAG, "Possible deadlock of the thread, not finished after ${deadlock / 1000}s:\n${exception.stackTraceToString()}") + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.possible_deadlock_title), + text = generalGetString(MR.strings.possible_deadlock_desc).format(deadlock / 1000, exception.stackTraceToString()), + ) + } + action() + job.cancel() + if (appPreferences.developerTools.get() && appPreferences.showSlowApiCalls.get()) { + val end = System.currentTimeMillis() + if (end - start > slow) { + Log.e(TAG, "Possible problem with execution of the thread, took ${(end - start) / 1000}s:\n${exception.stackTraceToString()}") + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.possible_slow_function_title), + text = generalGetString(MR.strings.possible_slow_function_desc).format((end - start) / 1000, exception.stackTraceToString()), + ) + } + } +} + +@OptIn(InternalCoroutinesApi::class) +suspend fun interruptIfCancelled() = coroutineScope { + if (!isActive) { + Log.d(TAG, "Coroutine was cancelled and interrupted: ${Exception().stackTraceToString()}") + throw coroutineContext.job.getCancellationException() + } +} + +/** + * This coroutine helper makes possible to cancel coroutine scope when a user goes back but not when the user rotates a screen + * */ +@Composable +fun ModalData.CancellableOnGoneJob(key: String = rememberSaveable { UUID.randomUUID().toString() }): MutableState { + val job = remember { stateGetOrPut(key) { Job() } } + DisposableEffectOnGone { + job.value.cancel() + } + return job +} enum class KeyboardState { Opened, Closed @@ -422,8 +479,12 @@ fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {} val orientation = windowOrientation() onDispose { whenDispose() - if (orientation == windowOrientation()) { - whenGone() + withApi { + // It needs some delay before check orientation again because it can still be not updated to actual value + delay(300) + if (orientation == windowOrientation()) { + whenGone() + } } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index eca579bcc..ee0302890 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -36,7 +36,7 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) { val rhId = rh?.remoteHostId AddGroupLayout( createGroup = { incognito, groupProfile -> - withApi { + withBGApi { val groupInfo = chatModel.controller.apiNewGroup(rhId, incognito, groupProfile) if (groupInfo != null) { chatModel.addChat(Chat(remoteHostId = rhId, chatInfo = ChatInfo.Group(groupInfo), chatItems = listOf())) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt index adbacca6b..5b56fe5e3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ConnectPlan.kt @@ -56,7 +56,7 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_connect_to_yourself), text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText, confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, onDismiss = cleanup, onDismissRequest = cleanup, destructive = true, @@ -134,7 +134,7 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_connect_to_yourself), text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText, confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, destructive = true, onDismiss = cleanup, onDismissRequest = cleanup, @@ -157,7 +157,7 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_repeat_connection_request), text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText, confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb), - onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, onDismiss = cleanup, onDismissRequest = cleanup, destructive = true, @@ -223,7 +223,7 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_via_group_link), text = generalGetString(MR.strings.you_will_join_group) + linkText, confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), - onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, onDismiss = cleanup, onDismissRequest = cleanup, hostDevice = hostDevice(rhId), @@ -254,7 +254,7 @@ suspend fun planAndConnect( title = generalGetString(MR.strings.connect_plan_repeat_join_request), text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link) + linkText, confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button), - onConfirm = { withApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, + onConfirm = { withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }, onDismiss = cleanup, onDismissRequest = cleanup, destructive = true, @@ -374,7 +374,7 @@ fun askCurrentOrIncognitoProfileAlert( val connectColor = if (connectDestructive) MaterialTheme.colors.error else MaterialTheme.colors.primary SectionItemView({ AlertManager.privacySensitive.hideAlert() - withApi { + withBGApi { connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup) } }) { @@ -382,7 +382,7 @@ fun askCurrentOrIncognitoProfileAlert( } SectionItemView({ AlertManager.privacySensitive.hideAlert() - withApi { + withBGApi { connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup) } }) { @@ -402,7 +402,7 @@ fun askCurrentOrIncognitoProfileAlert( } fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, contact: Contact) { - withApi { + withBGApi { val c = chatModel.getContactChat(contact.contactId) if (c != null) { close?.invoke() @@ -439,7 +439,7 @@ fun ownGroupLinkConfirmConnect( // Join incognito / Join with current profile SectionItemView({ AlertManager.privacySensitive.hideAlert() - withApi { + withBGApi { connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup) } }) { @@ -452,7 +452,7 @@ fun ownGroupLinkConfirmConnect( // Use current profile SectionItemView({ AlertManager.privacySensitive.hideAlert() - withApi { + withBGApi { connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup) } }) { @@ -461,7 +461,7 @@ fun ownGroupLinkConfirmConnect( // Use new incognito profile SectionItemView({ AlertManager.privacySensitive.hideAlert() - withApi { + withBGApi { connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup) } }) { @@ -483,7 +483,7 @@ fun ownGroupLinkConfirmConnect( } fun openKnownGroup(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, groupInfo: GroupInfo) { - withApi { + withBGApi { val g = chatModel.getGroupChat(groupInfo.groupId) if (g != null) { close?.invoke() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index 7fa0e6a70..6202d8263 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -192,7 +192,7 @@ fun DeleteButton(onClick: () -> Unit) { ) } -private fun setContactAlias(rhId: Long?, contactConnection: PendingContactConnection, localAlias: String, chatModel: ChatModel) = withApi { +private fun setContactAlias(rhId: Long?, contactConnection: PendingContactConnection, localAlias: String, chatModel: ChatModel) = withBGApi { chatModel.controller.apiSetConnectionAlias(rhId, contactConnection.pccConnId, localAlias)?.let { chatModel.updateContactConnection(rhId, it) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 0686f3c86..3b4bb86e6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -371,7 +371,7 @@ private fun createInvitation( ) { if (connReqInvitation.isNotEmpty() || contactConnection.value != null || creatingConnReq.value) return creatingConnReq.value = true - withApi { + withBGApi { val (r, alert) = controller.apiAddContact(rhId, incognito = controller.appPrefs.incognito.get()) if (r != null) { chatModel.updateContactConnection(rhId, r.second) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt index a5442a5bc..ec0ded450 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt @@ -43,7 +43,7 @@ fun CreateSimpleXAddress(m: ChatModel, rhId: Long?) { ) }, createAddress = { - withApi { + withBGApi { progressIndicator = true val connReqContact = m.controller.apiCreateUserAddress(rhId) if (connReqContact != null) { @@ -170,8 +170,8 @@ private fun ProgressIndicator() { private fun prepareChatBeforeAddressCreation(rhId: Long?) { if (chatModel.users.isNotEmpty()) return - withApi { - val user = chatModel.controller.apiGetActiveUser(rhId) ?: return@withApi + withBGApi { + val user = chatModel.controller.apiGetActiveUser(rhId) ?: return@withBGApi chatModel.currentUser.value = user if (chatModel.users.isEmpty()) { if (appPlatform.isDesktop) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt index a51d9c8a0..9dd3e20c2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -50,7 +50,7 @@ fun SetupDatabasePassphrase(m: ChatModel) { confirmNewKey, progressIndicator, onConfirmEncrypt = { - withApi { + withBGApi { if (m.chatRunning.value == true) { // Stop chat if it's started before doing anything stopChatAsync(m) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt index c06265e70..bf7884e9d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt @@ -38,7 +38,9 @@ import chat.simplex.common.views.usersettings.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.runBlocking @Composable fun ConnectMobileView() { @@ -46,7 +48,9 @@ fun ConnectMobileView() { val remoteHosts = remember { chatModel.remoteHosts } val deviceName = chatModel.controller.appPrefs.deviceNameForRemoteAccess LaunchedEffect(Unit) { - controller.reloadRemoteHosts() + withBGApi { + controller.reloadRemoteHosts() + } } ConnectMobileLayout( deviceName = remember { deviceName.state }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt index 29a507c69..7683f0ed1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt @@ -92,7 +92,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) { } fun saveCfg(cfg: NetCfg) { - withApi { + withBGApi { chatModel.controller.apiSetNetworkConfig(cfg) currentCfg.value = cfg chatModel.controller.setNetCfg(cfg) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index ff0b0fe1d..02a6e354e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -131,7 +131,7 @@ object AppearanceScope { SectionItemView({ val overrides = ThemeManager.currentThemeOverridesForExport(isInDarkTheme) theme.value = yaml.encodeToString(overrides) - withApi { exportThemeLauncher.launch("simplex.theme")} + withBGApi { exportThemeLauncher.launch("simplex.theme")} }) { Text(generalGetString(MR.strings.export_theme), color = colors.primary) } @@ -144,7 +144,7 @@ object AppearanceScope { } } // Can not limit to YAML mime type since it's unsupported by Android - SectionItemView({ withApi { importThemeLauncher.launch("*/*") } }) { + SectionItemView({ withBGApi { importThemeLauncher.launch("*/*") } }) { Text(generalGetString(MR.strings.import_theme), color = colors.primary) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt index 969a6d9d9..cc268e9a9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt @@ -1,6 +1,7 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer +import SectionSpacer import SectionTextFooter import SectionView import androidx.compose.foundation.layout.Column @@ -22,7 +23,7 @@ import chat.simplex.res.MR @Composable fun DeveloperView( m: ChatModel, - showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), + showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) { Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) { @@ -30,28 +31,34 @@ fun DeveloperView( AppBarTitle(stringResource(MR.strings.settings_developer_tools)) val developerTools = m.controller.appPrefs.developerTools val devTools = remember { developerTools.state } - SectionView() { + SectionView { InstallTerminalAppItem(uriHandler) - ChatConsoleItem { withAuth(generalGetString(MR.strings.auth_open_chat_console), generalGetString(MR.strings.auth_log_in_using_credential), showCustomModal { it, close -> TerminalView(it, close) })} - SettingsPreferenceItem(painterResource(MR.images.ic_drive_folder_upload), stringResource(MR.strings.confirm_database_upgrades), m.controller.appPrefs.confirmDBUpgrades) + ChatConsoleItem { withAuth(generalGetString(MR.strings.auth_open_chat_console), generalGetString(MR.strings.auth_log_in_using_credential), showCustomModal { it, close -> TerminalView(it, close) }) } SettingsPreferenceItem(painterResource(MR.images.ic_code), stringResource(MR.strings.show_developer_options), developerTools) - if (appPlatform.isDesktop && devTools.value) { - TerminalAlwaysVisibleItem(m.controller.appPrefs.terminalAlwaysVisible) { checked -> - if (checked) { - withAuth(generalGetString(MR.strings.auth_open_chat_console), generalGetString(MR.strings.auth_log_in_using_credential)) { - m.controller.appPrefs.terminalAlwaysVisible.set(true) + SectionTextFooter( + generalGetString(if (devTools.value) MR.strings.show_dev_options else MR.strings.hide_dev_options) + " " + + generalGetString(MR.strings.developer_options) + ) + } + if (devTools.value) { + SectionSpacer() + SectionView(stringResource(MR.strings.developer_options_section).uppercase()) { + SettingsPreferenceItem(painterResource(MR.images.ic_drive_folder_upload), stringResource(MR.strings.confirm_database_upgrades), m.controller.appPrefs.confirmDBUpgrades) + if (appPlatform.isDesktop) { + TerminalAlwaysVisibleItem(m.controller.appPrefs.terminalAlwaysVisible) { checked -> + if (checked) { + withAuth(generalGetString(MR.strings.auth_open_chat_console), generalGetString(MR.strings.auth_log_in_using_credential)) { + m.controller.appPrefs.terminalAlwaysVisible.set(true) + } + } else { + m.controller.appPrefs.terminalAlwaysVisible.set(false) } - } else { - m.controller.appPrefs.terminalAlwaysVisible.set(false) } } SettingsPreferenceItem(painterResource(MR.images.ic_report), stringResource(MR.strings.show_internal_errors), appPreferences.showInternalErrors) + SettingsPreferenceItem(painterResource(MR.images.ic_avg_pace), stringResource(MR.strings.show_slow_api_calls), appPreferences.showSlowApiCalls) } } - SectionTextFooter( - generalGetString(if (devTools.value) MR.strings.show_dev_options else MR.strings.hide_dev_options) + " " + - generalGetString(MR.strings.developer_options) - ) SectionBottomSpacer() } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index fcd602ee2..7c921d7e8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -37,7 +37,7 @@ fun NetworkAndServersView( chatModel: ChatModel, showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), + showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), ) { val currentRemoteHost by remember { chatModel.currentRemoteHost } // It's not a state, just a one-time value. Shouldn't be used in any state-related situations @@ -69,7 +69,7 @@ fun NetworkAndServersView( text = generalGetString(MR.strings.network_enable_socks_info).format(proxyPort.value), confirmText = generalGetString(MR.strings.confirm_verb), onConfirm = { - withApi { + withBGApi { val conf = NetCfg.proxyDefaults.withHostPort(chatModel.controller.appPrefs.networkProxyHostPort.get()) chatModel.controller.apiSetNetworkConfig(conf) chatModel.controller.setNetCfg(conf) @@ -84,7 +84,7 @@ fun NetworkAndServersView( text = generalGetString(MR.strings.network_disable_socks_info), confirmText = generalGetString(MR.strings.confirm_verb), onConfirm = { - withApi { + withBGApi { val conf = NetCfg.defaults chatModel.controller.apiSetNetworkConfig(conf) chatModel.controller.setNetCfg(conf) @@ -111,7 +111,7 @@ fun NetworkAndServersView( onionHosts.value = prevValue } ) { - withApi { + withBGApi { val newCfg = chatModel.controller.getNetCfg().withOnionHosts(it) val res = chatModel.controller.apiSetNetworkConfig(newCfg) if (res) { @@ -136,7 +136,7 @@ fun NetworkAndServersView( startsWith, onDismiss = { sessionMode.value = prevValue } ) { - withApi { + withBGApi { val newCfg = chatModel.controller.getNetCfg().copy(sessionMode = it) val res = chatModel.controller.apiSetNetworkConfig(newCfg) if (res) { @@ -160,7 +160,7 @@ fun NetworkAndServersView( proxyPort: State, showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), + showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), toggleSocksProxy: (Boolean) -> Unit, useOnion: (OnionHosts) -> Unit, updateSessionMode: (TransportSessionMode) -> Unit, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt index c375fc545..8b7ac6820 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt @@ -26,7 +26,7 @@ fun PreferencesView(m: ChatModel, user: User, close: () -> Unit,) { close() } fun savePrefs(afterSave: () -> Unit = {}) { - withApi { + withBGApi { val newProfile = user.profile.toProfile().copy(preferences = preferences.toPreferences()) val updated = m.controller.apiUpdateProfile(user.remoteHostId, newProfile) if (updated != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index f163a5464..e6ec2ba23 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -96,7 +96,7 @@ fun PrivacySettingsView( val currentUser = chatModel.currentUser.value if (currentUser != null) { fun setSendReceiptsContacts(enable: Boolean, clearOverrides: Boolean) { - withApi { + withBGApi { val mrs = UserMsgReceiptSettings(enable, clearOverrides) chatModel.controller.apiSetUserContactReceipts(currentUser, mrs) chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) @@ -119,7 +119,7 @@ fun PrivacySettingsView( } fun setSendReceiptsGroups(enable: Boolean, clearOverrides: Boolean) { - withApi { + withBGApi { val mrs = UserMsgReceiptSettings(enable, clearOverrides) chatModel.controller.apiSetUserGroupReceipts(currentUser, mrs) chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt index 08ebc4ef1..cb3b16b22 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt @@ -28,21 +28,19 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.QRCode import chat.simplex.common.model.ChatModel import chat.simplex.res.MR +import kotlinx.coroutines.* import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch @Composable fun ProtocolServerView(m: ChatModel, server: ServerCfg, serverProtocol: ServerProtocol, onUpdate: (ServerCfg) -> Unit, onDelete: () -> Unit) { var testing by remember { mutableStateOf(false) } - val scope = rememberCoroutineScope() ProtocolServerLayout( testing, server, serverProtocol, testServer = { testing = true - scope.launch { + withLongRunningApi { val res = testServerConnection(server, m) if (isActive) { onUpdate(res.first) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt index 66dde9f96..e1668ab9a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt @@ -23,12 +23,10 @@ import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* import chat.simplex.common.platform.appPlatform -import chat.simplex.common.views.usersettings.ScanProtocolServer import chat.simplex.res.MR -import kotlinx.coroutines.launch @Composable -fun ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtocol, close: () -> Unit) { +fun ModalData.ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtocol, close: () -> Unit) { var presetServers by remember(rhId) { mutableStateOf(emptyList()) } var servers by remember(rhId) { mutableStateOf(m.userSMPServersUnsaved.value ?: emptyList()) @@ -56,16 +54,18 @@ fun ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtoco } LaunchedEffect(rhId) { - val res = m.controller.getUserProtoServers(rhId, serverProtocol) - if (res != null) { - currServers.value = res.protoServers - presetServers = res.presetServers - if (servers.isEmpty()) { - servers = currServers.value + withApi { + val res = m.controller.getUserProtoServers(rhId, serverProtocol) + if (res != null) { + currServers.value = res.protoServers + presetServers = res.presetServers + if (servers.isEmpty()) { + servers = currServers.value + } } } } - + val testServersJob = CancellableOnGoneJob() fun showServer(server: ServerCfg) { ModalManager.start.showModalCloseable(true) { close -> var old by remember { mutableStateOf(server) } @@ -91,7 +91,6 @@ fun ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtoco }) } } - val scope = rememberCoroutineScope() ModalView( close = { if (saveDisabled.value) close() @@ -148,7 +147,7 @@ fun ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtoco ) }, testServers = { - scope.launch { + testServersJob.value = withLongRunningApi { testServers(testing, servers, m) { servers = it m.userSMPServersUnsaved.value = servers @@ -338,6 +337,7 @@ private suspend fun runServersTest(servers: List, m: ChatModel, onUpd val updatedServers = ArrayList(servers) for ((index, server) in servers.withIndex()) { if (server.enabled) { + interruptIfCancelled() val (updatedServer, f) = testServerConnection(server, m) updatedServers.removeAt(index) updatedServers.add(index, updatedServer) @@ -352,7 +352,7 @@ private suspend fun runServersTest(servers: List, m: ChatModel, onUpd } private fun saveServers(rhId: Long?, protocol: ServerProtocol, currServers: MutableState>, servers: List, m: ChatModel, afterSave: () -> Unit = {}) { - withApi { + withBGApi { if (m.controller.setUserProtoServers(rhId, protocol, servers)) { currServers.value = servers m.userSMPServersUnsaved.value = null diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt index b75f52268..c18fd3f6f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt @@ -24,7 +24,7 @@ fun SetDeliveryReceiptsView(m: ChatModel) { enableReceipts = { val currentUser = m.currentUser.value if (currentUser != null) { - withApi { + withBGApi { try { m.controller.apiSetAllContactReceipts(currentUser.remoteHostId, enable = true) m.currentUser.value = currentUser.copy(sendRcptsContacts = true) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index 0f37fb197..ccc893085 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -62,7 +62,7 @@ fun SettingsView(chatModel: ChatModel, setPerformLA: (Boolean) -> Unit, drawerSt }, showCustomModal = { modalView -> { ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } } }, showVersion = { - withApi { + withBGApi { val info = chatModel.controller.apiGetVersion() if (info != null) { ModalManager.start.showModal { VersionInfoView(info) } @@ -89,7 +89,7 @@ fun SettingsLayout( showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), showSettingsModalWithSearch: (@Composable (ChatModel, MutableState) -> Unit) -> Unit, - showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), + showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit, drawerState: DrawerState, @@ -186,7 +186,7 @@ fun SettingsLayout( @Composable expect fun SettingsSectionApp( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), + showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) @@ -218,16 +218,14 @@ expect fun SettingsSectionApp( } } -@Composable fun ChatPreferencesItem(showCustomModal: ((@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit)), stopped: Boolean) { +@Composable fun ChatPreferencesItem(showCustomModal: ((@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit)), stopped: Boolean) { SettingsActionItem( painterResource(MR.images.ic_toggle_on), stringResource(MR.strings.chat_preferences), click = if (stopped) null else ({ - withApi { - showCustomModal { m, close -> - PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close) - }() - } + showCustomModal { m, close -> + PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close) + }() }), disabled = stopped, extraPadding = true diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index 98b8b26f4..ffaec8eb0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -70,7 +70,7 @@ fun UserAddressView( shareViaProfile, onCloseHandler, createAddress = { - withApi { + withBGApi { progressIndicator = true val connReqContact = chatModel.controller.apiCreateUserAddress(user?.value?.remoteHostId) if (connReqContact != null) { @@ -116,7 +116,7 @@ fun UserAddressView( confirmText = generalGetString(MR.strings.delete_verb), onConfirm = { progressIndicator = true - withApi { + withBGApi { val u = chatModel.controller.apiDeleteUserAddress(user?.value?.remoteHostId) if (u != null) { chatModel.userAddress.value = null diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt index bf7c4ac80..1f5177d08 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt @@ -40,7 +40,7 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) { profile = profile, close, saveProfile = { displayName, fullName, image -> - withApi { + withBGApi { val updated = chatModel.controller.apiUpdateProfile(user.remoteHostId, profile.copy(displayName = displayName.trim(), fullName = fullName, image = image)) if (updated != null) { val (newProfile, _) = updated diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index dcfdbb0b5..9a885f34e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -139,6 +139,10 @@ Delete file Error deleting user profile Error updating user privacy + Deadlock + Execution of code takes too long time: %1$d seconds. Probably, the app is frozen: %2$s + Slow function + Execution of function takes too long time: %1$d seconds: %2$s Instant notifications @@ -686,7 +690,9 @@ Hide: Show developer options Database IDs and Transport isolation option. + Developer options Show internal errors + Show slow API calls Shutdown? Notifications will stop working until you re-launch the app diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_avg_pace.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_avg_pace.svg new file mode 100644 index 000000000..d3052a6c8 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_avg_pace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index 57371e25a..6e7945d8c 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -186,7 +186,7 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState) { } } // Reload all strings in all @Composable's after language change at runtime - if (remember { ChatController.appPrefs.terminalAlwaysVisible.state }.value && remember { ChatController.appPrefs.appLanguage.state }.value != "") { + if (remember { ChatController.appPrefs.developerTools.state }.value && remember { ChatController.appPrefs.terminalAlwaysVisible.state }.value && remember { ChatController.appPrefs.appLanguage.state }.value != "") { var hiddenUntilRestart by remember { mutableStateOf(false) } if (!hiddenUntilRestart) { val cWindowState = rememberWindowState(placement = WindowPlacement.Floating, width = DEFAULT_START_MODAL_WIDTH, height = 768.dp) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt index b40b892de..0b86652a2 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Share.desktop.kt @@ -4,8 +4,7 @@ import androidx.compose.ui.platform.ClipboardManager import androidx.compose.ui.platform.UriHandler import androidx.compose.ui.text.AnnotatedString import chat.simplex.common.model.* -import chat.simplex.common.views.helpers.generalGetString -import chat.simplex.common.views.helpers.withApi +import chat.simplex.common.views.helpers.* import java.io.File import java.net.URI import java.net.URLEncoder @@ -23,7 +22,7 @@ actual fun ClipboardManager.shareText(text: String) { } actual fun shareFile(text: String, fileSource: CryptoFile) { - withApi { + withBGApi { FileChooserLauncher(false) { to: URI? -> if (to != null) { val absolutePath = if (fileSource.isAbsolutePath) fileSource.filePath else getAppFilePath(fileSource.filePath) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt index 905a6e352..13d82cbcc 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.desktop.kt @@ -35,7 +35,7 @@ actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserL ItemAction(stringResource(MR.strings.save_verb), painterResource(if (cItem.file?.fileSource?.cryptoArgs == null) MR.images.ic_download else MR.images.ic_lock_open_right), onClick = { val saveIfExists = { when (cItem.content.msgContent) { - is MsgContent.MCImage, is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> withApi { saveFileLauncher.launch(cItem.file?.fileName ?: "") } + is MsgContent.MCImage, is MsgContent.MCFile, is MsgContent.MCVoice, is MsgContent.MCVideo -> withBGApi { saveFileLauncher.launch(cItem.file?.fileName ?: "") } else -> {} } showMenu.value = false diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt index 61e0e0d3c..13d2f8b1e 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt @@ -43,7 +43,7 @@ actual fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow Unit) -> (() -> Unit), - showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit), + showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), showVersion: () -> Unit, withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) {