android, desktop: moving to single thread in api calls (#3670)
* android, desktop: moving to single thread in api calls * more places * more changes * seconds * long running api into init function * changes * developer options * progress indicator * string * rename * progressIndicator for stop chat
This commit is contained in:
parent
bc8a6f4833
commit
dad9716915
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
) {
|
||||
|
@ -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,7 +466,7 @@ object ChatController {
|
||||
suspend fun sendCmd(rhId: Long?, cmd: CC): CR {
|
||||
val ctrl = ctrl ?: throw Exception("Controller is not initialized")
|
||||
|
||||
return withContext(Dispatchers.IO) {
|
||||
//return withContext(Dispatchers.IO) {
|
||||
val c = cmd.cmdString
|
||||
chatModel.addTerminalItem(TerminalItem.cmd(rhId, cmd.obfuscated))
|
||||
Log.d(TAG, "sendCmd: ${cmd.cmdType}")
|
||||
@ -475,8 +477,8 @@ object ChatController {
|
||||
Log.d(TAG, "sendCmd response json $json")
|
||||
}
|
||||
chatModel.addTerminalItem(TerminalItem.resp(rhId, r.resp))
|
||||
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,12 +1866,10 @@ object ChatController {
|
||||
}
|
||||
withCall(r, r.contact) { _ ->
|
||||
chatModel.callCommand.add(WCallCommand.End)
|
||||
withApi {
|
||||
chatModel.activeCall.value = null
|
||||
chatModel.showCallView.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
is CR.ContactSwitch ->
|
||||
chatModel.updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats)
|
||||
is CR.GroupMemberSwitch ->
|
||||
@ -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,10 +2005,9 @@ 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)
|
||||
@ -2019,7 +2019,6 @@ object ChatController {
|
||||
chatModel.networkStatuses.putAll(ss)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun activeUser(rhId: Long?, user: UserLike): Boolean =
|
||||
rhId == chatModel.remoteHostId() && user.userId == chatModel.currentUser.value?.userId
|
||||
|
@ -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<Any> = chatMigrateInit(dbAbsolutePrefixPath, dbKey, confirm.value)
|
||||
val res: DBMigrationResult = kotlin.runCatching {
|
||||
json.decodeFromString<DBMigrationResult>(migrated[0] as String)
|
||||
|
@ -50,7 +50,7 @@ private fun sendCommand(chatModel: ChatModel, composeState: MutableState<Compose
|
||||
chatModel.addTerminalItem(TerminalItem.resp(null, resp))
|
||||
composeState.value = ComposeState(useLinkPreviews = false)
|
||||
} else {
|
||||
withApi {
|
||||
withBGApi {
|
||||
// show "in progress"
|
||||
// TODO show active remote host in chat console?
|
||||
chatModel.controller.sendCmd(chatModel.remoteHostId(), CC.Console(s))
|
||||
|
@ -175,8 +175,8 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> 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) {
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
},
|
||||
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)
|
||||
}
|
||||
}) {
|
||||
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)
|
||||
}
|
||||
}) {
|
||||
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)
|
||||
}
|
||||
}) {
|
||||
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)
|
||||
|
@ -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<ConnectionStats?, Profile?>? = 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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
||||
},
|
||||
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)
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -101,7 +101,7 @@ fun CIFileView(
|
||||
filePath = getLoadedFilePath(file)
|
||||
}
|
||||
if (filePath != null) {
|
||||
withApi {
|
||||
withBGApi {
|
||||
saveFileLauncher.launch(file.fileName)
|
||||
}
|
||||
} else {
|
||||
|
@ -394,7 +394,7 @@ fun JoinGroupAction(
|
||||
inProgress: MutableState<Boolean>
|
||||
) {
|
||||
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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,15 +117,17 @@ fun UserPicker(
|
||||
LaunchedEffect(Unit) {
|
||||
// Controller.ctrl can be null when self-destructing activates
|
||||
if (controller.ctrl != null && controller.ctrl != -1L) {
|
||||
withBGApi {
|
||||
controller.reloadRemoteHosts()
|
||||
}
|
||||
}
|
||||
}
|
||||
val UsersView: @Composable ColumnScope.() -> Unit = {
|
||||
users.forEach { u ->
|
||||
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)
|
||||
|
@ -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) }
|
||||
)
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ fun DatabaseEncryptionView(m: ChatModel) {
|
||||
initialRandomDBPassphrase,
|
||||
progressIndicator,
|
||||
onConfirmEncrypt = {
|
||||
withApi {
|
||||
withBGApi {
|
||||
encryptDatabase(currentKey, newKey, confirmNewKey, initialRandomDBPassphrase, useKeychain, storedKey, progressIndicator)
|
||||
}
|
||||
}
|
||||
|
@ -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<Instant?>, chatDbChanged: MutableState<Boolean>) {
|
||||
withApi {
|
||||
fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged: MutableState<Boolean>, progressIndicator: MutableState<Boolean>? = null) {
|
||||
withBGApi {
|
||||
try {
|
||||
progressIndicator?.value = true
|
||||
if (chatDbChanged.value) {
|
||||
initChatController()
|
||||
chatDbChanged.value = false
|
||||
@ -376,12 +378,12 @@ fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, 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<Instant?>, 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<Boolean>? = 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<Boolean>? = 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<Boolean>? = 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<Boolea
|
||||
|
||||
private fun deleteChat(m: ChatModel, progressIndicator: MutableState<Boolean>) {
|
||||
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
|
||||
|
@ -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 {
|
||||
|
@ -45,7 +45,7 @@ class ModalData {
|
||||
}
|
||||
|
||||
class ModalManager(private val placement: ModalPlacement? = null) {
|
||||
private val modalViews = arrayListOf<Pair<Boolean, (@Composable (close: () -> Unit) -> Unit)>>()
|
||||
private val modalViews = arrayListOf<Triple<Boolean, ModalData, (@Composable ModalData.(close: () -> Unit) -> Unit)>>()
|
||||
private val modalCount = mutableStateOf(0)
|
||||
private val toRemove = mutableSetOf<Int>()
|
||||
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 } }
|
||||
|
@ -43,9 +43,8 @@ class ProcessedErrors <T: AgentErrorType>(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,
|
||||
|
@ -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<Job> {
|
||||
val job = remember { stateGetOrPut<Job>(key) { Job() } }
|
||||
DisposableEffectOnGone {
|
||||
job.value.cancel()
|
||||
}
|
||||
return job
|
||||
}
|
||||
|
||||
enum class KeyboardState {
|
||||
Opened, Closed
|
||||
@ -422,11 +479,15 @@ fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {}
|
||||
val orientation = windowOrientation()
|
||||
onDispose {
|
||||
whenDispose()
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
@ -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()))
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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,8 +48,10 @@ fun ConnectMobileView() {
|
||||
val remoteHosts = remember { chatModel.remoteHosts }
|
||||
val deviceName = chatModel.controller.appPrefs.deviceNameForRemoteAccess
|
||||
LaunchedEffect(Unit) {
|
||||
withBGApi {
|
||||
controller.reloadRemoteHosts()
|
||||
}
|
||||
}
|
||||
ConnectMobileLayout(
|
||||
deviceName = remember { deviceName.state },
|
||||
remoteHosts = remoteHosts,
|
||||
|
@ -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)
|
||||
|
@ -131,7 +131,7 @@ object AppearanceScope {
|
||||
SectionItemView({
|
||||
val overrides = ThemeManager.currentThemeOverridesForExport(isInDarkTheme)
|
||||
theme.value = yaml.encodeToString<ThemeOverrides>(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)
|
||||
}
|
||||
}
|
||||
|
@ -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,12 +31,20 @@ 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) {
|
||||
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)) {
|
||||
@ -45,13 +54,11 @@ fun DeveloperView(
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -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<Int>,
|
||||
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,
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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<String>()) }
|
||||
var servers by remember(rhId) {
|
||||
mutableStateOf(m.userSMPServersUnsaved.value ?: emptyList())
|
||||
@ -56,6 +54,7 @@ fun ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtoco
|
||||
}
|
||||
|
||||
LaunchedEffect(rhId) {
|
||||
withApi {
|
||||
val res = m.controller.getUserProtoServers(rhId, serverProtocol)
|
||||
if (res != null) {
|
||||
currServers.value = res.protoServers
|
||||
@ -65,7 +64,8 @@ fun ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtoco
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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<ServerCfg>, m: ChatModel, onUpd
|
||||
val updatedServers = ArrayList<ServerCfg>(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<ServerCfg>, m: ChatModel, onUpd
|
||||
}
|
||||
|
||||
private fun saveServers(rhId: Long?, protocol: ServerProtocol, currServers: MutableState<List<ServerCfg>>, servers: List<ServerCfg>, m: ChatModel, afterSave: () -> Unit = {}) {
|
||||
withApi {
|
||||
withBGApi {
|
||||
if (m.controller.setUserProtoServers(rhId, protocol, servers)) {
|
||||
currServers.value = servers
|
||||
m.userSMPServersUnsaved.value = null
|
||||
|
@ -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)
|
||||
|
@ -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<String>) -> 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)
|
||||
}()
|
||||
}
|
||||
}),
|
||||
disabled = stopped,
|
||||
extraPadding = true
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -139,6 +139,10 @@
|
||||
<string name="smp_server_test_delete_file">Delete file</string>
|
||||
<string name="error_deleting_user">Error deleting user profile</string>
|
||||
<string name="error_updating_user_privacy">Error updating user privacy</string>
|
||||
<string name="possible_deadlock_title">Deadlock</string>
|
||||
<string name="possible_deadlock_desc">Execution of code takes too long time: %1$d seconds. Probably, the app is frozen: %2$s</string>
|
||||
<string name="possible_slow_function_title">Slow function</string>
|
||||
<string name="possible_slow_function_desc">Execution of function takes too long time: %1$d seconds: %2$s</string>
|
||||
|
||||
<!-- background service notice - SimpleXAPI.kt -->
|
||||
<string name="icon_descr_instant_notifications">Instant notifications</string>
|
||||
@ -686,7 +690,9 @@
|
||||
<string name="hide_dev_options">Hide:</string>
|
||||
<string name="show_developer_options">Show developer options</string>
|
||||
<string name="developer_options">Database IDs and Transport isolation option.</string>
|
||||
<string name="developer_options_section">Developer options</string>
|
||||
<string name="show_internal_errors">Show internal errors</string>
|
||||
<string name="show_slow_api_calls">Show slow API calls</string>
|
||||
<string name="shutdown_alert_question">Shutdown?</string>
|
||||
<string name="shutdown_alert_desc">Notifications will stop working until you re-launch the app</string>
|
||||
|
||||
|
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M609.824-771.5q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm0 660q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm160-520q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm0 380q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25Zm60-190q-15.824 0-27.074-11.426-11.25-11.426-11.25-27.25t11.426-27.324q11.426-11.5 27.25-11.5t27.324 11.676q11.5 11.676 11.5 27.5t-11.676 27.074q-11.676 11.25-27.5 11.25ZM480-81.5q-82.481 0-155.275-31.304-72.794-31.305-126.706-85.219-53.913-53.915-85.216-126.711Q81.5-397.531 81.5-480.016q0-82.484 31.303-155.273 31.303-72.79 85.216-126.699 53.912-53.909 126.706-85.461Q397.519-879 480-879v60q-141.5 0-240 98.562Q141.5-621.875 141.5-480t98.312 240.188Q338.125-141.5 480-141.5v60Zm-.111-330q-28.389 0-48.389-20.078-20-20.078-20-48.422 0-5.938.75-12.441.75-6.503 3.25-11.808L336-584l40-40.5 81.023 79.5q4.477-2 22.977-4 28.344 0 48.672 20.361Q549-508.279 549-479.889q0 28.389-20.361 48.389-20.36 20-48.75 20Z"/></svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -186,7 +186,7 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState<Boolean>) {
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -43,7 +43,7 @@ actual fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<An
|
||||
.combinedClickable(onClick = {
|
||||
val chat = chatModel.getChat(call.contact.id)
|
||||
if (chat != null) {
|
||||
withApi {
|
||||
withBGApi {
|
||||
openChat(chat.remoteHostId, chat.chatInfo, chatModel)
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package chat.simplex.common.views.database
|
||||
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import chat.simplex.common.platform.chatModel
|
||||
import chat.simplex.common.views.helpers.withApi
|
||||
import chat.simplex.common.views.helpers.withBGApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
@ -12,7 +12,7 @@ actual fun restartChatOrApp() {
|
||||
startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged)
|
||||
} else {
|
||||
authStopChat(chatModel) {
|
||||
withApi {
|
||||
withBGApi {
|
||||
// adding delay in order to prevent locked database by previous initialization
|
||||
delay(1000)
|
||||
chatModel.chatDbChanged.value = true
|
||||
|
@ -45,7 +45,7 @@ actual fun GetImageBottomSheet(
|
||||
}
|
||||
val pickImageLauncher = rememberFileChooserLauncher(true, null, processPickedImage)
|
||||
ActionButton(null, stringResource(MR.strings.from_gallery_button), icon = painterResource(MR.images.ic_image)) {
|
||||
withApi { pickImageLauncher.launch("image/*") }
|
||||
withBGApi { pickImageLauncher.launch("image/*") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ fun AppearanceScope.AppearanceLayout(
|
||||
val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") }
|
||||
LangSelector(state) {
|
||||
state.value = it
|
||||
withApi {
|
||||
withBGApi {
|
||||
delay(200)
|
||||
if (it == "system") {
|
||||
languagePref.set(null)
|
||||
|
@ -3,6 +3,7 @@ package chat.simplex.common.views.usersettings
|
||||
import SectionView
|
||||
import androidx.compose.runtime.Composable
|
||||
import chat.simplex.common.model.ChatModel
|
||||
import chat.simplex.common.views.helpers.ModalData
|
||||
import chat.simplex.res.MR
|
||||
import dev.icerock.moko.resources.compose.painterResource
|
||||
import dev.icerock.moko.resources.compose.stringResource
|
||||
@ -10,7 +11,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
|
||||
) {
|
||||
|
Loading…
Reference in New Issue
Block a user