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.io.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
const val TAG = "SIMPLEX"
|
const val TAG = "SIMPLEX"
|
||||||
|
|
||||||
@ -72,7 +71,7 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
|||||||
|
|
||||||
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||||
Log.d(TAG, "onStateChanged: $event")
|
Log.d(TAG, "onStateChanged: $event")
|
||||||
withApi {
|
withBGApi {
|
||||||
when (event) {
|
when (event) {
|
||||||
Lifecycle.Event.ON_START -> {
|
Lifecycle.Event.ON_START -> {
|
||||||
isAppOnForeground = true
|
isAppOnForeground = true
|
||||||
|
@ -104,7 +104,7 @@ class SimplexService: Service() {
|
|||||||
if (wakeLock != null || isStartingService) return
|
if (wakeLock != null || isStartingService) return
|
||||||
val self = this
|
val self = this
|
||||||
isStartingService = true
|
isStartingService = true
|
||||||
withApi {
|
withBGApi {
|
||||||
val chatController = ChatController
|
val chatController = ChatController
|
||||||
waitDbMigrationEnds(chatController)
|
waitDbMigrationEnds(chatController)
|
||||||
try {
|
try {
|
||||||
@ -114,7 +114,7 @@ class SimplexService: Service() {
|
|||||||
Log.w(chat.simplex.app.TAG, "SimplexService: problem with the database: $chatDbStatus")
|
Log.w(chat.simplex.app.TAG, "SimplexService: problem with the database: $chatDbStatus")
|
||||||
showPassphraseNotification(chatDbStatus)
|
showPassphraseNotification(chatDbStatus)
|
||||||
safeStopService()
|
safeStopService()
|
||||||
return@withApi
|
return@withBGApi
|
||||||
}
|
}
|
||||||
saveServiceState(self, ServiceState.STARTED)
|
saveServiceState(self, ServiceState.STARTED)
|
||||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run {
|
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.model.MsgContent
|
||||||
import chat.simplex.common.platform.FileChooserLauncher
|
import chat.simplex.common.platform.FileChooserLauncher
|
||||||
import chat.simplex.common.platform.saveImage
|
import chat.simplex.common.platform.saveImage
|
||||||
import chat.simplex.common.views.helpers.SharedContent
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.helpers.withApi
|
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
@ -37,7 +36,7 @@ actual fun SaveContentItemAction(cItem: ChatItem, saveFileLauncher: FileChooserL
|
|||||||
writePermissionState.launchPermissionRequest()
|
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 -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
showMenu.value = false
|
showMenu.value = false
|
||||||
|
@ -102,7 +102,7 @@ fun AppearanceScope.AppearanceLayout(
|
|||||||
val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") }
|
val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") }
|
||||||
LangSelector(state) {
|
LangSelector(state) {
|
||||||
state.value = it
|
state.value = it
|
||||||
withApi {
|
withBGApi {
|
||||||
delay(200)
|
delay(200)
|
||||||
val activity = context as? Activity
|
val activity = context as? Activity
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
|
@ -5,8 +5,7 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import chat.simplex.common.model.ChatModel
|
import chat.simplex.common.model.ChatModel
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.views.helpers.AlertManager
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.helpers.generalGetString
|
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import com.jakewharton.processphoenix.ProcessPhoenix
|
import com.jakewharton.processphoenix.ProcessPhoenix
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
@ -15,7 +14,7 @@ import dev.icerock.moko.resources.compose.stringResource
|
|||||||
@Composable
|
@Composable
|
||||||
actual fun SettingsSectionApp(
|
actual fun SettingsSectionApp(
|
||||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
||||||
showVersion: () -> Unit,
|
showVersion: () -> Unit,
|
||||||
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
|
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
|
||||||
) {
|
) {
|
||||||
|
@ -110,6 +110,7 @@ class AppPreferences {
|
|||||||
val chatStopped = mkBoolPreference(SHARED_PREFS_CHAT_STOPPED, false)
|
val chatStopped = mkBoolPreference(SHARED_PREFS_CHAT_STOPPED, false)
|
||||||
val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false)
|
val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false)
|
||||||
val showInternalErrors = mkBoolPreference(SHARED_PREFS_SHOW_INTERNAL_ERRORS, 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 terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false)
|
||||||
val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false)
|
val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false)
|
||||||
val networkProxyHostPort = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, "localhost:9050")
|
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_CHAT_STOPPED = "ChatStopped"
|
||||||
private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools"
|
private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools"
|
||||||
private const val SHARED_PREFS_SHOW_INTERNAL_ERRORS = "ShowInternalErrors"
|
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_TERMINAL_ALWAYS_VISIBLE = "TerminalAlwaysVisible"
|
||||||
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
|
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
|
||||||
private const val SHARED_PREFS_NETWORK_PROXY_HOST_PORT = "NetworkProxyHostPort"
|
private const val SHARED_PREFS_NETWORK_PROXY_HOST_PORT = "NetworkProxyHostPort"
|
||||||
@ -464,7 +466,7 @@ object ChatController {
|
|||||||
suspend fun sendCmd(rhId: Long?, cmd: CC): CR {
|
suspend fun sendCmd(rhId: Long?, cmd: CC): CR {
|
||||||
val ctrl = ctrl ?: throw Exception("Controller is not initialized")
|
val ctrl = ctrl ?: throw Exception("Controller is not initialized")
|
||||||
|
|
||||||
return withContext(Dispatchers.IO) {
|
//return withContext(Dispatchers.IO) {
|
||||||
val c = cmd.cmdString
|
val c = cmd.cmdString
|
||||||
chatModel.addTerminalItem(TerminalItem.cmd(rhId, cmd.obfuscated))
|
chatModel.addTerminalItem(TerminalItem.cmd(rhId, cmd.obfuscated))
|
||||||
Log.d(TAG, "sendCmd: ${cmd.cmdType}")
|
Log.d(TAG, "sendCmd: ${cmd.cmdType}")
|
||||||
@ -475,8 +477,8 @@ object ChatController {
|
|||||||
Log.d(TAG, "sendCmd response json $json")
|
Log.d(TAG, "sendCmd response json $json")
|
||||||
}
|
}
|
||||||
chatModel.addTerminalItem(TerminalItem.resp(rhId, r.resp))
|
chatModel.addTerminalItem(TerminalItem.resp(rhId, r.resp))
|
||||||
r.resp
|
return r.resp
|
||||||
}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recvMsg(ctrl: ChatCtrl): APIResponse? {
|
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.MCImage && file.fileSize <= MAX_IMAGE_SIZE_AUTO_RCV)
|
||||||
|| (mc is MsgContent.MCVideo && file.fileSize <= MAX_VIDEO_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))) {
|
|| (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)) {
|
if (cItem.showNotification && (allowedToShowNotification() || chatModel.chatId.value != cInfo.id || chatModel.remoteHostId() != rhId)) {
|
||||||
ntfManager.notifyMessageReceived(r.user, cInfo, cItem)
|
ntfManager.notifyMessageReceived(r.user, cInfo, cItem)
|
||||||
@ -1863,12 +1866,10 @@ object ChatController {
|
|||||||
}
|
}
|
||||||
withCall(r, r.contact) { _ ->
|
withCall(r, r.contact) { _ ->
|
||||||
chatModel.callCommand.add(WCallCommand.End)
|
chatModel.callCommand.add(WCallCommand.End)
|
||||||
withApi {
|
|
||||||
chatModel.activeCall.value = null
|
chatModel.activeCall.value = null
|
||||||
chatModel.showCallView.value = false
|
chatModel.showCallView.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
is CR.ContactSwitch ->
|
is CR.ContactSwitch ->
|
||||||
chatModel.updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats)
|
chatModel.updateContactConnectionStats(rhId, r.contact, r.switchProgress.connectionStats)
|
||||||
is CR.GroupMemberSwitch ->
|
is CR.GroupMemberSwitch ->
|
||||||
@ -1977,7 +1978,7 @@ object ChatController {
|
|||||||
r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.CRITICAL -> {
|
r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.CRITICAL -> {
|
||||||
chatModel.processedCriticalError.newError(r.chatError.agentError, r.chatError.agentError.offerRestart)
|
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)
|
chatModel.processedInternalError.newError(r.chatError.agentError, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2004,10 +2005,9 @@ object ChatController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun switchToLocalSession() {
|
suspend fun switchToLocalSession() {
|
||||||
val m = chatModel
|
val m = chatModel
|
||||||
m.remoteCtrlSession.value = null
|
m.remoteCtrlSession.value = null
|
||||||
withBGApi {
|
|
||||||
val users = listUsers(null)
|
val users = listUsers(null)
|
||||||
m.users.clear()
|
m.users.clear()
|
||||||
m.users.addAll(users)
|
m.users.addAll(users)
|
||||||
@ -2019,7 +2019,6 @@ object ChatController {
|
|||||||
chatModel.networkStatuses.putAll(ss)
|
chatModel.networkStatuses.putAll(ss)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun activeUser(rhId: Long?, user: UserLike): Boolean =
|
private fun activeUser(rhId: Long?, user: UserLike): Boolean =
|
||||||
rhId == chatModel.remoteHostId() && user.userId == chatModel.currentUser.value?.userId
|
rhId == chatModel.remoteHostId() && user.userId == chatModel.currentUser.value?.userId
|
||||||
|
@ -42,7 +42,7 @@ val appPreferences: AppPreferences
|
|||||||
val chatController: ChatController = ChatController
|
val chatController: ChatController = ChatController
|
||||||
|
|
||||||
fun initChatControllerAndRunMigrations() {
|
fun initChatControllerAndRunMigrations() {
|
||||||
withBGApi {
|
withLongRunningApi(slow = 30_000, deadlock = 60_000) {
|
||||||
if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) {
|
if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) {
|
||||||
initChatController(startChat = ::showStartChatAfterRestartAlert)
|
initChatController(startChat = ::showStartChatAfterRestartAlert)
|
||||||
} else {
|
} else {
|
||||||
@ -57,7 +57,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat
|
|||||||
if (chatModel.ctrlInitInProgress.value) return
|
if (chatModel.ctrlInitInProgress.value) return
|
||||||
chatModel.ctrlInitInProgress.value = true
|
chatModel.ctrlInitInProgress.value = true
|
||||||
val dbKey = useKey ?: DatabaseUtils.useDatabaseKey()
|
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 migrated: Array<Any> = chatMigrateInit(dbAbsolutePrefixPath, dbKey, confirm.value)
|
||||||
val res: DBMigrationResult = kotlin.runCatching {
|
val res: DBMigrationResult = kotlin.runCatching {
|
||||||
json.decodeFromString<DBMigrationResult>(migrated[0] as String)
|
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))
|
chatModel.addTerminalItem(TerminalItem.resp(null, resp))
|
||||||
composeState.value = ComposeState(useLinkPreviews = false)
|
composeState.value = ComposeState(useLinkPreviews = false)
|
||||||
} else {
|
} else {
|
||||||
withApi {
|
withBGApi {
|
||||||
// show "in progress"
|
// show "in progress"
|
||||||
// TODO show active remote host in chat console?
|
// TODO show active remote host in chat console?
|
||||||
chatModel.controller.sendCmd(chatModel.remoteHostId(), CC.Console(s))
|
chatModel.controller.sendCmd(chatModel.remoteHostId(), CC.Console(s))
|
||||||
|
@ -175,8 +175,8 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) {
|
fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null)) ?: return@withApi
|
val user = controller.apiCreateActiveUser(null, Profile(displayName.trim(), "", null)) ?: return@withBGApi
|
||||||
controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress)
|
controller.appPrefs.onboardingStage.set(OnboardingStage.Step3_CreateSimpleXAddress)
|
||||||
chatModel.chatRunning.value = false
|
chatModel.chatRunning.value = false
|
||||||
controller.startChat(user)
|
controller.startChat(user)
|
||||||
@ -186,11 +186,11 @@ fun createProfileInNoProfileSetup(displayName: String, close: () -> Unit) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () -> Unit) {
|
fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: () -> Unit) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val rhId = chatModel.remoteHostId()
|
val rhId = chatModel.remoteHostId()
|
||||||
val user = chatModel.controller.apiCreateActiveUser(
|
val user = chatModel.controller.apiCreateActiveUser(
|
||||||
rhId, Profile(displayName.trim(), "", null)
|
rhId, Profile(displayName.trim(), "", null)
|
||||||
) ?: return@withApi
|
) ?: return@withBGApi
|
||||||
chatModel.currentUser.value = user
|
chatModel.currentUser.value = user
|
||||||
if (chatModel.users.isEmpty()) {
|
if (chatModel.users.isEmpty()) {
|
||||||
chatModel.controller.startChat(user)
|
chatModel.controller.startChat(user)
|
||||||
@ -206,10 +206,10 @@ fun createProfileInProfiles(chatModel: ChatModel, displayName: String, close: ()
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () -> Unit) {
|
fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () -> Unit) {
|
||||||
withApi {
|
withBGApi {
|
||||||
chatModel.currentUser.value = chatModel.controller.apiCreateActiveUser(
|
chatModel.currentUser.value = chatModel.controller.apiCreateActiveUser(
|
||||||
null, Profile(displayName.trim(), "", null)
|
null, Profile(displayName.trim(), "", null)
|
||||||
) ?: return@withApi
|
) ?: return@withBGApi
|
||||||
val onboardingStage = chatModel.controller.appPrefs.onboardingStage
|
val onboardingStage = chatModel.controller.appPrefs.onboardingStage
|
||||||
if (chatModel.users.isEmpty()) {
|
if (chatModel.users.isEmpty()) {
|
||||||
onboardingStage.set(if (appPlatform.isDesktop && chatModel.controller.appPrefs.initialRandomDBPassphrase.get() && !chatModel.desktopOnboardingRandomPassword.value) {
|
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.model.ChatModel
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.views.helpers.withApi
|
import chat.simplex.common.views.helpers.withBGApi
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import kotlin.time.Duration.Companion.minutes
|
import kotlin.time.Duration.Companion.minutes
|
||||||
|
|
||||||
@ -28,13 +28,13 @@ class CallManager(val chatModel: ChatModel) {
|
|||||||
if (call == null) {
|
if (call == null) {
|
||||||
justAcceptIncomingCall(invitation = invitation)
|
justAcceptIncomingCall(invitation = invitation)
|
||||||
} else {
|
} else {
|
||||||
withApi {
|
withBGApi {
|
||||||
chatModel.switchingCall.value = true
|
chatModel.switchingCall.value = true
|
||||||
try {
|
try {
|
||||||
endCall(call = call)
|
endCall(call = call)
|
||||||
justAcceptIncomingCall(invitation = invitation)
|
justAcceptIncomingCall(invitation = invitation)
|
||||||
} finally {
|
} finally {
|
||||||
withApi { chatModel.switchingCall.value = false }
|
chatModel.switchingCall.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ class CallManager(val chatModel: ChatModel) {
|
|||||||
activeCallInvitation.value = null
|
activeCallInvitation.value = null
|
||||||
ntfManager.cancelCallNotification()
|
ntfManager.cancelCallNotification()
|
||||||
}
|
}
|
||||||
withApi {
|
withBGApi {
|
||||||
if (!controller.apiRejectCall(invitation.remoteHostId, invitation.contact)) {
|
if (!controller.apiRejectCall(invitation.remoteHostId, invitation.contact)) {
|
||||||
Log.e(TAG, "apiRejectCall error")
|
Log.e(TAG, "apiRejectCall error")
|
||||||
}
|
}
|
||||||
|
@ -69,11 +69,9 @@ fun ChatInfoView(
|
|||||||
currentUser,
|
currentUser,
|
||||||
sendReceipts = sendReceipts,
|
sendReceipts = sendReceipts,
|
||||||
setSendReceipts = { sendRcpts ->
|
setSendReceipts = { sendRcpts ->
|
||||||
withApi {
|
|
||||||
val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(sendRcpts = sendRcpts.bool)
|
val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(sendRcpts = sendRcpts.bool)
|
||||||
updateChatSettings(chat, chatSettings, chatModel)
|
updateChatSettings(chat, chatSettings, chatModel)
|
||||||
sendReceipts.value = sendRcpts
|
sendReceipts.value = sendRcpts
|
||||||
}
|
|
||||||
},
|
},
|
||||||
connStats = connStats,
|
connStats = connStats,
|
||||||
contactNetworkStatus.value,
|
contactNetworkStatus.value,
|
||||||
@ -96,7 +94,7 @@ fun ChatInfoView(
|
|||||||
clearChat = { clearChatDialog(chat, chatModel, close) },
|
clearChat = { clearChatDialog(chat, chatModel, close) },
|
||||||
switchContactAddress = {
|
switchContactAddress = {
|
||||||
showSwitchAddressAlert(switchAddress = {
|
showSwitchAddressAlert(switchAddress = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val cStats = chatModel.controller.apiSwitchContact(chatRh, contact.contactId)
|
val cStats = chatModel.controller.apiSwitchContact(chatRh, contact.contactId)
|
||||||
connStats.value = cStats
|
connStats.value = cStats
|
||||||
if (cStats != null) {
|
if (cStats != null) {
|
||||||
@ -108,7 +106,7 @@ fun ChatInfoView(
|
|||||||
},
|
},
|
||||||
abortSwitchContactAddress = {
|
abortSwitchContactAddress = {
|
||||||
showAbortSwitchAddressAlert(abortSwitchAddress = {
|
showAbortSwitchAddressAlert(abortSwitchAddress = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val cStats = chatModel.controller.apiAbortSwitchContact(chatRh, contact.contactId)
|
val cStats = chatModel.controller.apiAbortSwitchContact(chatRh, contact.contactId)
|
||||||
connStats.value = cStats
|
connStats.value = cStats
|
||||||
if (cStats != null) {
|
if (cStats != null) {
|
||||||
@ -118,7 +116,7 @@ fun ChatInfoView(
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
syncContactConnection = {
|
syncContactConnection = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
|
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
|
||||||
connStats.value = cStats
|
connStats.value = cStats
|
||||||
if (cStats != null) {
|
if (cStats != null) {
|
||||||
@ -129,7 +127,7 @@ fun ChatInfoView(
|
|||||||
},
|
},
|
||||||
syncContactConnectionForce = {
|
syncContactConnectionForce = {
|
||||||
showSyncConnectionForceAlert(syncConnectionForce = {
|
showSyncConnectionForceAlert(syncConnectionForce = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = true)
|
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = true)
|
||||||
connStats.value = cStats
|
connStats.value = cStats
|
||||||
if (cStats != null) {
|
if (cStats != null) {
|
||||||
@ -208,18 +206,14 @@ fun deleteContactDialog(chat: Chat, chatModel: ChatModel, close: (() -> Unit)? =
|
|||||||
// Delete and notify contact
|
// Delete and notify contact
|
||||||
SectionItemView({
|
SectionItemView({
|
||||||
AlertManager.shared.hideAlert()
|
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)
|
Text(generalGetString(MR.strings.delete_and_notify_contact), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.error)
|
||||||
}
|
}
|
||||||
// Delete
|
// Delete
|
||||||
SectionItemView({
|
SectionItemView({
|
||||||
AlertManager.shared.hideAlert()
|
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)
|
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
|
// Delete
|
||||||
SectionItemView({
|
SectionItemView({
|
||||||
AlertManager.shared.hideAlert()
|
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)
|
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) {
|
fun deleteContact(chat: Chat, chatModel: ChatModel, close: (() -> Unit)?, notify: Boolean? = null) {
|
||||||
val chatInfo = chat.chatInfo
|
val chatInfo = chat.chatInfo
|
||||||
withApi {
|
withBGApi {
|
||||||
val chatRh = chat.remoteHostId
|
val chatRh = chat.remoteHostId
|
||||||
val r = chatModel.controller.apiDeleteChat(chatRh, chatInfo.chatType, chatInfo.apiId, notify)
|
val r = chatModel.controller.apiDeleteChat(chatRh, chatInfo.chatType, chatInfo.apiId, notify)
|
||||||
if (r) {
|
if (r) {
|
||||||
@ -269,7 +261,7 @@ fun clearChatDialog(chat: Chat, chatModel: ChatModel, close: (() -> Unit)? = nul
|
|||||||
text = generalGetString(MR.strings.clear_chat_warning),
|
text = generalGetString(MR.strings.clear_chat_warning),
|
||||||
confirmText = generalGetString(MR.strings.clear_verb),
|
confirmText = generalGetString(MR.strings.clear_verb),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val chatRh = chat.remoteHostId
|
val chatRh = chat.remoteHostId
|
||||||
val updatedChatInfo = chatModel.controller.apiClearChat(chatRh, chatInfo.chatType, chatInfo.apiId)
|
val updatedChatInfo = chatModel.controller.apiClearChat(chatRh, chatInfo.chatType, chatInfo.apiId)
|
||||||
if (updatedChatInfo != null) {
|
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
|
val chatRh = chat.remoteHostId
|
||||||
chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let {
|
chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let {
|
||||||
chatModel.updateContact(chatRh, it)
|
chatModel.updateContact(chatRh, it)
|
||||||
|
@ -164,7 +164,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
return@ChatLayout
|
return@ChatLayout
|
||||||
}
|
}
|
||||||
hideKeyboard(view)
|
hideKeyboard(view)
|
||||||
withApi {
|
withBGApi {
|
||||||
// The idea is to preload information before showing a modal because large groups can take time to load all members
|
// 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 preloadedContactInfo: Pair<ConnectionStats?, Profile?>? = null
|
||||||
var preloadedCode: String? = null
|
var preloadedCode: String? = null
|
||||||
@ -205,7 +205,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
},
|
},
|
||||||
showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
|
showMemberInfo = { groupInfo: GroupInfo, member: GroupMember ->
|
||||||
hideKeyboard(view)
|
hideKeyboard(view)
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
|
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
|
||||||
val stats = r?.second
|
val stats = r?.second
|
||||||
val (_, code) = if (member.memberActive) {
|
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 c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout)
|
||||||
val firstId = chatModel.chatItems.firstOrNull()?.id
|
val firstId = chatModel.chatItems.firstOrNull()?.id
|
||||||
if (c != null && firstId != null) {
|
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}")
|
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)
|
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}")
|
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 ->
|
deleteMessage = { itemId, mode ->
|
||||||
withApi {
|
withBGApi {
|
||||||
val cInfo = chat.chatInfo
|
val cInfo = chat.chatInfo
|
||||||
val toDeleteItem = chatModel.chatItems.firstOrNull { it.id == itemId }
|
val toDeleteItem = chatModel.chatItems.firstOrNull { it.id == itemId }
|
||||||
val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo)
|
val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo)
|
||||||
@ -291,13 +291,13 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
receiveFile = { fileId, encrypted ->
|
receiveFile = { fileId, encrypted ->
|
||||||
withApi { chatModel.controller.receiveFile(chatRh, user, fileId, encrypted) }
|
withBGApi { chatModel.controller.receiveFile(chatRh, user, fileId, encrypted) }
|
||||||
},
|
},
|
||||||
cancelFile = { fileId ->
|
cancelFile = { fileId ->
|
||||||
withApi { chatModel.controller.cancelFile(chatRh, user, fileId) }
|
withBGApi { chatModel.controller.cancelFile(chatRh, user, fileId) }
|
||||||
},
|
},
|
||||||
joinGroup = { groupId, onComplete ->
|
joinGroup = { groupId, onComplete ->
|
||||||
withApi {
|
withBGApi {
|
||||||
chatModel.controller.apiJoinGroup(chatRh, groupId)
|
chatModel.controller.apiJoinGroup(chatRh, groupId)
|
||||||
onComplete.invoke()
|
onComplete.invoke()
|
||||||
}
|
}
|
||||||
@ -314,11 +314,11 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
},
|
},
|
||||||
endCall = {
|
endCall = {
|
||||||
val call = chatModel.activeCall.value
|
val call = chatModel.activeCall.value
|
||||||
if (call != null) withApi { chatModel.callManager.endCall(call) }
|
if (call != null) withBGApi { chatModel.callManager.endCall(call) }
|
||||||
},
|
},
|
||||||
acceptCall = { contact ->
|
acceptCall = { contact ->
|
||||||
hideKeyboard(view)
|
hideKeyboard(view)
|
||||||
withApi {
|
withBGApi {
|
||||||
val invitation = chatModel.callInvitations.remove(contact.id)
|
val invitation = chatModel.callInvitations.remove(contact.id)
|
||||||
?: controller.apiGetCallInvitations(chatModel.remoteHostId()).firstOrNull { it.contact.id == contact.id }
|
?: controller.apiGetCallInvitations(chatModel.remoteHostId()).firstOrNull { it.contact.id == contact.id }
|
||||||
if (invitation == null) {
|
if (invitation == null) {
|
||||||
@ -329,17 +329,17 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
acceptFeature = { contact, feature, param ->
|
acceptFeature = { contact, feature, param ->
|
||||||
withApi {
|
withBGApi {
|
||||||
chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param)
|
chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
openDirectChat = { contactId ->
|
openDirectChat = { contactId ->
|
||||||
withApi {
|
withBGApi {
|
||||||
openDirectChat(chatRh, contactId, chatModel)
|
openDirectChat(chatRh, contactId, chatModel)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateContactStats = { contact ->
|
updateContactStats = { contact ->
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
|
val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId)
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
val contactStats = r.first
|
val contactStats = r.first
|
||||||
@ -349,7 +349,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateMemberStats = { groupInfo, member ->
|
updateMemberStats = { groupInfo, member ->
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
|
val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId)
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
val memStats = r.second
|
val memStats = r.second
|
||||||
@ -360,7 +360,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
syncContactConnection = { contact ->
|
syncContactConnection = { contact ->
|
||||||
withApi {
|
withBGApi {
|
||||||
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
|
val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false)
|
||||||
if (cStats != null) {
|
if (cStats != null) {
|
||||||
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
|
chatModel.updateContactConnectionStats(chatRh, contact, cStats)
|
||||||
@ -368,7 +368,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
syncMemberConnection = { groupInfo, member ->
|
syncMemberConnection = { groupInfo, member ->
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false)
|
val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false)
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second)
|
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 }
|
chatModel.groupMembers.find { it.id == memberId }
|
||||||
},
|
},
|
||||||
setReaction = { cInfo, cItem, add, reaction ->
|
setReaction = { cInfo, cItem, add, reaction ->
|
||||||
withApi {
|
withBGApi {
|
||||||
val updatedCI = chatModel.controller.apiChatItemReaction(
|
val updatedCI = chatModel.controller.apiChatItemReaction(
|
||||||
rh = chatRh,
|
rh = chatRh,
|
||||||
type = cInfo.chatType,
|
type = cInfo.chatType,
|
||||||
@ -397,7 +397,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
showItemDetails = { cInfo, cItem ->
|
showItemDetails = { cInfo, cItem ->
|
||||||
withApi {
|
withBGApi {
|
||||||
val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id)
|
val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id)
|
||||||
if (ciInfo != null) {
|
if (ciInfo != null) {
|
||||||
if (chat.chatInfo is ChatInfo.Group) {
|
if (chat.chatInfo is ChatInfo.Group) {
|
||||||
@ -416,7 +416,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
},
|
},
|
||||||
addMembers = { groupInfo ->
|
addMembers = { groupInfo ->
|
||||||
hideKeyboard(view)
|
hideKeyboard(view)
|
||||||
withApi {
|
withBGApi {
|
||||||
setGroupMembers(chatRh, groupInfo, chatModel)
|
setGroupMembers(chatRh, groupInfo, chatModel)
|
||||||
ModalManager.end.closeModals()
|
ModalManager.end.closeModals()
|
||||||
ModalManager.end.showModalCloseable(true) { close ->
|
ModalManager.end.showModalCloseable(true) { close ->
|
||||||
@ -426,7 +426,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
},
|
},
|
||||||
openGroupLink = { groupInfo ->
|
openGroupLink = { groupInfo ->
|
||||||
hideKeyboard(view)
|
hideKeyboard(view)
|
||||||
withApi {
|
withBGApi {
|
||||||
val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId)
|
val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId)
|
||||||
ModalManager.end.closeModals()
|
ModalManager.end.closeModals()
|
||||||
ModalManager.end.showModalCloseable(true) {
|
ModalManager.end.showModalCloseable(true) {
|
||||||
@ -451,7 +451,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId:
|
|||||||
if (searchText.value == value) return@ChatLayout
|
if (searchText.value == value) return@ChatLayout
|
||||||
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
|
if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout
|
||||||
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout
|
val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout
|
||||||
withApi {
|
withBGApi {
|
||||||
apiFindMessages(c, chatModel, value)
|
apiFindMessages(c, chatModel, value)
|
||||||
searchText.value = value
|
searchText.value = value
|
||||||
}
|
}
|
||||||
|
@ -267,7 +267,7 @@ fun ComposeView(
|
|||||||
fun loadLinkPreview(url: String, wait: Long? = null) {
|
fun loadLinkPreview(url: String, wait: Long? = null) {
|
||||||
if (pendingLinkUrl.value == url) {
|
if (pendingLinkUrl.value == url) {
|
||||||
composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(null))
|
composeState.value = composeState.value.copy(preview = ComposePreview.CLinkPreview(null))
|
||||||
withApi {
|
withBGApi {
|
||||||
if (wait != null) delay(wait)
|
if (wait != null) delay(wait)
|
||||||
val lp = getLinkPreview(url)
|
val lp = getLinkPreview(url)
|
||||||
if (lp != null && pendingLinkUrl.value == url) {
|
if (lp != null && pendingLinkUrl.value == url) {
|
||||||
@ -575,7 +575,7 @@ fun ComposeView(
|
|||||||
|
|
||||||
fun allowVoiceToContact() {
|
fun allowVoiceToContact() {
|
||||||
val contact = (chat.chatInfo as ChatInfo.Direct?)?.contact ?: return
|
val contact = (chat.chatInfo as ChatInfo.Direct?)?.contact ?: return
|
||||||
withApi {
|
withBGApi {
|
||||||
chatModel.controller.allowFeatureToContact(chat.remoteHostId, contact, ChatFeature.Voice)
|
chatModel.controller.allowFeatureToContact(chat.remoteHostId, contact, ChatFeature.Voice)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ fun ContactPreferencesView(
|
|||||||
var currentFeaturesAllowed by rememberSaveable(ct, stateSaver = serializableSaver()) { mutableStateOf(featuresAllowed) }
|
var currentFeaturesAllowed by rememberSaveable(ct, stateSaver = serializableSaver()) { mutableStateOf(featuresAllowed) }
|
||||||
|
|
||||||
fun savePrefs(afterSave: () -> Unit = {}) {
|
fun savePrefs(afterSave: () -> Unit = {}) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val prefs = contactFeaturesAllowedToPrefs(featuresAllowed)
|
val prefs = contactFeaturesAllowedToPrefs(featuresAllowed)
|
||||||
val toContact = m.controller.apiSetContactPrefs(rhId, ct.contactId, prefs)
|
val toContact = m.controller.apiSetContactPrefs(rhId, ct.contactId, prefs)
|
||||||
if (toContact != null) {
|
if (toContact != null) {
|
||||||
|
@ -54,7 +54,7 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea
|
|||||||
},
|
},
|
||||||
inviteMembers = {
|
inviteMembers = {
|
||||||
allowModifyMembers = false
|
allowModifyMembers = false
|
||||||
withApi {
|
withBGApi {
|
||||||
for (contactId in selectedContacts) {
|
for (contactId in selectedContacts) {
|
||||||
val member = chatModel.controller.apiAddMember(rhId, groupInfo.groupId, contactId, selectedRole.value)
|
val member = chatModel.controller.apiAddMember(rhId, groupInfo.groupId, contactId, selectedRole.value)
|
||||||
if (member != null) {
|
if (member != null) {
|
||||||
@ -68,7 +68,7 @@ fun AddGroupMembersView(rhId: Long?, groupInfo: GroupInfo, creatingGroup: Boolea
|
|||||||
},
|
},
|
||||||
clearSelection = { selectedContacts.clear() },
|
clearSelection = { selectedContacts.clear() },
|
||||||
addContact = { contactId -> if (contactId !in selectedContacts) selectedContacts.add(contactId) },
|
addContact = { contactId -> if (contactId !in selectedContacts) selectedContacts.add(contactId) },
|
||||||
removeContact = { contactId -> selectedContacts.removeIf { it == contactId } },
|
removeContact = { contactId -> selectedContacts.removeAll { it == contactId } },
|
||||||
close = close,
|
close = close,
|
||||||
)
|
)
|
||||||
KeyChangeEffect(chatModel.chatId.value) {
|
KeyChangeEffect(chatModel.chatId.value) {
|
||||||
|
@ -56,11 +56,9 @@ fun GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLi
|
|||||||
currentUser,
|
currentUser,
|
||||||
sendReceipts = sendReceipts,
|
sendReceipts = sendReceipts,
|
||||||
setSendReceipts = { sendRcpts ->
|
setSendReceipts = { sendRcpts ->
|
||||||
withApi {
|
|
||||||
val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(sendRcpts = sendRcpts.bool)
|
val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(sendRcpts = sendRcpts.bool)
|
||||||
updateChatSettings(chat, chatSettings, chatModel)
|
updateChatSettings(chat, chatSettings, chatModel)
|
||||||
sendReceipts.value = sendRcpts
|
sendReceipts.value = sendRcpts
|
||||||
}
|
|
||||||
},
|
},
|
||||||
members = chatModel.groupMembers
|
members = chatModel.groupMembers
|
||||||
.filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
|
.filter { it.memberStatus != GroupMemberStatus.MemLeft && it.memberStatus != GroupMemberStatus.MemRemoved }
|
||||||
@ -68,7 +66,7 @@ fun GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLi
|
|||||||
developerTools,
|
developerTools,
|
||||||
groupLink,
|
groupLink,
|
||||||
addMembers = {
|
addMembers = {
|
||||||
withApi {
|
withBGApi {
|
||||||
setGroupMembers(rhId, groupInfo, chatModel)
|
setGroupMembers(rhId, groupInfo, chatModel)
|
||||||
ModalManager.end.showModalCloseable(true) { close ->
|
ModalManager.end.showModalCloseable(true) { close ->
|
||||||
AddGroupMembersView(rhId, groupInfo, false, chatModel, close)
|
AddGroupMembersView(rhId, groupInfo, false, chatModel, close)
|
||||||
@ -76,7 +74,7 @@ fun GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLi
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
showMemberInfo = { member ->
|
showMemberInfo = { member ->
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiGroupMemberInfo(rhId, groupInfo.groupId, member.groupMemberId)
|
val r = chatModel.controller.apiGroupMemberInfo(rhId, groupInfo.groupId, member.groupMemberId)
|
||||||
val stats = r?.second
|
val stats = r?.second
|
||||||
val (_, code) = if (member.memberActive) {
|
val (_, code) = if (member.memberActive) {
|
||||||
@ -131,7 +129,7 @@ fun deleteGroupDialog(chat: Chat, groupInfo: GroupInfo, chatModel: ChatModel, cl
|
|||||||
text = generalGetString(alertTextKey),
|
text = generalGetString(alertTextKey),
|
||||||
confirmText = generalGetString(MR.strings.delete_verb),
|
confirmText = generalGetString(MR.strings.delete_verb),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiDeleteChat(chat.remoteHostId, chatInfo.chatType, chatInfo.apiId)
|
val r = chatModel.controller.apiDeleteChat(chat.remoteHostId, chatInfo.chatType, chatInfo.apiId)
|
||||||
if (r) {
|
if (r) {
|
||||||
chatModel.removeChat(chat.remoteHostId, chatInfo.id)
|
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),
|
text = generalGetString(MR.strings.you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved),
|
||||||
confirmText = generalGetString(MR.strings.leave_group_button),
|
confirmText = generalGetString(MR.strings.leave_group_button),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
withApi {
|
withBGApi {
|
||||||
chatModel.controller.leaveGroup(rhId, groupInfo.groupId)
|
chatModel.controller.leaveGroup(rhId, groupInfo.groupId)
|
||||||
close?.invoke()
|
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),
|
text = generalGetString(MR.strings.member_will_be_removed_from_group_cannot_be_undone),
|
||||||
confirmText = generalGetString(MR.strings.remove_member_confirmation),
|
confirmText = generalGetString(MR.strings.remove_member_confirmation),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val updatedMember = chatModel.controller.apiRemoveMember(rhId, groupInfo.groupId, mem.groupMemberId)
|
val updatedMember = chatModel.controller.apiRemoveMember(rhId, groupInfo.groupId, mem.groupMemberId)
|
||||||
if (updatedMember != null) {
|
if (updatedMember != null) {
|
||||||
chatModel.upsertGroupMember(rhId, groupInfo, updatedMember)
|
chatModel.upsertGroupMember(rhId, groupInfo, updatedMember)
|
||||||
|
@ -38,7 +38,7 @@ fun GroupLinkView(
|
|||||||
var creatingLink by rememberSaveable { mutableStateOf(false) }
|
var creatingLink by rememberSaveable { mutableStateOf(false) }
|
||||||
fun createLink() {
|
fun createLink() {
|
||||||
creatingLink = true
|
creatingLink = true
|
||||||
withApi {
|
withBGApi {
|
||||||
val link = chatModel.controller.apiCreateGroupLink(rhId, groupInfo.groupId)
|
val link = chatModel.controller.apiCreateGroupLink(rhId, groupInfo.groupId)
|
||||||
if (link != null) {
|
if (link != null) {
|
||||||
groupLink = link.first
|
groupLink = link.first
|
||||||
@ -78,7 +78,7 @@ fun GroupLinkView(
|
|||||||
text = generalGetString(MR.strings.all_group_members_will_remain_connected),
|
text = generalGetString(MR.strings.all_group_members_will_remain_connected),
|
||||||
confirmText = generalGetString(MR.strings.delete_verb),
|
confirmText = generalGetString(MR.strings.delete_verb),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiDeleteGroupLink(rhId, groupInfo.groupId)
|
val r = chatModel.controller.apiDeleteGroupLink(rhId, groupInfo.groupId)
|
||||||
if (r) {
|
if (r) {
|
||||||
groupLink = null
|
groupLink = null
|
||||||
|
@ -66,7 +66,7 @@ fun GroupMemberInfoView(
|
|||||||
connectionCode,
|
connectionCode,
|
||||||
getContactChat = { chatModel.getContactChat(it) },
|
getContactChat = { chatModel.getContactChat(it) },
|
||||||
openDirectChat = {
|
openDirectChat = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val c = chatModel.controller.apiGetChat(rhId, ChatType.Direct, it)
|
val c = chatModel.controller.apiGetChat(rhId, ChatType.Direct, it)
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
if (chatModel.getContactChat(it) == null) {
|
if (chatModel.getContactChat(it) == null) {
|
||||||
@ -81,7 +81,7 @@ fun GroupMemberInfoView(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
createMemberContact = {
|
createMemberContact = {
|
||||||
withApi {
|
withBGApi {
|
||||||
progressIndicator = true
|
progressIndicator = true
|
||||||
val memberContact = chatModel.controller.apiCreateMemberContact(rhId, groupInfo.apiId, member.groupMemberId)
|
val memberContact = chatModel.controller.apiCreateMemberContact(rhId, groupInfo.apiId, member.groupMemberId)
|
||||||
if (memberContact != null) {
|
if (memberContact != null) {
|
||||||
@ -107,7 +107,7 @@ fun GroupMemberInfoView(
|
|||||||
updateMemberRoleDialog(it, member, onDismiss = {
|
updateMemberRoleDialog(it, member, onDismiss = {
|
||||||
newRole.value = prevValue
|
newRole.value = prevValue
|
||||||
}) {
|
}) {
|
||||||
withApi {
|
withBGApi {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val mem = chatModel.controller.apiMemberRole(rhId, groupInfo.groupId, member.groupMemberId, it)
|
val mem = chatModel.controller.apiMemberRole(rhId, groupInfo.groupId, member.groupMemberId, it)
|
||||||
chatModel.upsertGroupMember(rhId, groupInfo, mem)
|
chatModel.upsertGroupMember(rhId, groupInfo, mem)
|
||||||
@ -119,7 +119,7 @@ fun GroupMemberInfoView(
|
|||||||
},
|
},
|
||||||
switchMemberAddress = {
|
switchMemberAddress = {
|
||||||
showSwitchAddressAlert(switchAddress = {
|
showSwitchAddressAlert(switchAddress = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId)
|
val r = chatModel.controller.apiSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId)
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
connStats.value = r.second
|
connStats.value = r.second
|
||||||
@ -131,7 +131,7 @@ fun GroupMemberInfoView(
|
|||||||
},
|
},
|
||||||
abortSwitchMemberAddress = {
|
abortSwitchMemberAddress = {
|
||||||
showAbortSwitchAddressAlert(abortSwitchAddress = {
|
showAbortSwitchAddressAlert(abortSwitchAddress = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiAbortSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId)
|
val r = chatModel.controller.apiAbortSwitchGroupMember(rhId, groupInfo.apiId, member.groupMemberId)
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
connStats.value = r.second
|
connStats.value = r.second
|
||||||
@ -142,7 +142,7 @@ fun GroupMemberInfoView(
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
syncMemberConnection = {
|
syncMemberConnection = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = false)
|
val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = false)
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
connStats.value = r.second
|
connStats.value = r.second
|
||||||
@ -153,7 +153,7 @@ fun GroupMemberInfoView(
|
|||||||
},
|
},
|
||||||
syncMemberConnectionForce = {
|
syncMemberConnectionForce = {
|
||||||
showSyncConnectionForceAlert(syncConnectionForce = {
|
showSyncConnectionForceAlert(syncConnectionForce = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = true)
|
val r = chatModel.controller.apiSyncGroupMemberRatchet(rhId, groupInfo.apiId, member.groupMemberId, force = true)
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
connStats.value = r.second
|
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),
|
text = generalGetString(MR.strings.member_will_be_removed_from_group_cannot_be_undone),
|
||||||
confirmText = generalGetString(MR.strings.remove_member_confirmation),
|
confirmText = generalGetString(MR.strings.remove_member_confirmation),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val removedMember = chatModel.controller.apiRemoveMember(rhId, member.groupId, member.groupMemberId)
|
val removedMember = chatModel.controller.apiRemoveMember(rhId, member.groupId, member.groupMemberId)
|
||||||
if (removedMember != null) {
|
if (removedMember != null) {
|
||||||
chatModel.upsertGroupMember(rhId, groupInfo, removedMember)
|
chatModel.upsertGroupMember(rhId, groupInfo, removedMember)
|
||||||
@ -505,7 +505,7 @@ private fun updateMemberRoleDialog(
|
|||||||
fun connectViaMemberAddressAlert(rhId: Long?, connReqUri: String) {
|
fun connectViaMemberAddressAlert(rhId: Long?, connReqUri: String) {
|
||||||
try {
|
try {
|
||||||
val uri = URI(connReqUri)
|
val uri = URI(connReqUri)
|
||||||
withApi {
|
withBGApi {
|
||||||
planAndConnect(rhId, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() })
|
planAndConnect(rhId, uri, incognito = null, close = { ModalManager.closeAllModalsEverywhere() })
|
||||||
}
|
}
|
||||||
} catch (e: RuntimeException) {
|
} 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) }
|
var currentPreferences by rememberSaveable(gInfo, stateSaver = serializableSaver()) { mutableStateOf(preferences) }
|
||||||
|
|
||||||
fun savePrefs(afterSave: () -> Unit = {}) {
|
fun savePrefs(afterSave: () -> Unit = {}) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val gp = gInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences())
|
val gp = gInfo.groupProfile.copy(groupPreferences = preferences.toGroupPreferences())
|
||||||
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp)
|
val g = m.controller.apiUpdateGroup(rhId, gInfo.groupId, gp)
|
||||||
if (g != null) {
|
if (g != null) {
|
||||||
|
@ -35,7 +35,7 @@ fun GroupProfileView(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel, cl
|
|||||||
close = close,
|
close = close,
|
||||||
groupProfile = groupInfo.groupProfile,
|
groupProfile = groupInfo.groupProfile,
|
||||||
saveProfile = { p ->
|
saveProfile = { p ->
|
||||||
withApi {
|
withBGApi {
|
||||||
val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, p)
|
val gInfo = chatModel.controller.apiUpdateGroup(rhId, groupInfo.groupId, p)
|
||||||
if (gInfo != null) {
|
if (gInfo != null) {
|
||||||
chatModel.updateGroup(rhId, gInfo)
|
chatModel.updateGroup(rhId, gInfo)
|
||||||
|
@ -36,7 +36,7 @@ fun GroupWelcomeView(m: ChatModel, rhId: Long?, groupInfo: GroupInfo, close: ()
|
|||||||
val welcomeText = remember { mutableStateOf(gInfo.groupProfile.description ?: "") }
|
val welcomeText = remember { mutableStateOf(gInfo.groupProfile.description ?: "") }
|
||||||
|
|
||||||
fun save(afterSave: () -> Unit = {}) {
|
fun save(afterSave: () -> Unit = {}) {
|
||||||
withApi {
|
withBGApi {
|
||||||
var welcome: String? = welcomeText.value.trim('\n', ' ')
|
var welcome: String? = welcomeText.value.trim('\n', ' ')
|
||||||
if (welcome?.length == 0) {
|
if (welcome?.length == 0) {
|
||||||
welcome = null
|
welcome = null
|
||||||
|
@ -101,7 +101,7 @@ fun CIFileView(
|
|||||||
filePath = getLoadedFilePath(file)
|
filePath = getLoadedFilePath(file)
|
||||||
}
|
}
|
||||||
if (filePath != null) {
|
if (filePath != null) {
|
||||||
withApi {
|
withBGApi {
|
||||||
saveFileLauncher.launch(file.fileName)
|
saveFileLauncher.launch(file.fileName)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -394,7 +394,7 @@ fun JoinGroupAction(
|
|||||||
inProgress: MutableState<Boolean>
|
inProgress: MutableState<Boolean>
|
||||||
) {
|
) {
|
||||||
val joinGroup: () -> Unit = {
|
val joinGroup: () -> Unit = {
|
||||||
withApi {
|
withBGApi {
|
||||||
inProgress.value = true
|
inProgress.value = true
|
||||||
chatModel.controller.apiJoinGroup(chat.remoteHostId, groupInfo.groupId)
|
chatModel.controller.apiJoinGroup(chat.remoteHostId, groupInfo.groupId)
|
||||||
inProgress.value = false
|
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) {
|
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)
|
val contact = chatModel.controller.apiAcceptContactRequest(rhId, incognito, apiId)
|
||||||
if (contact != null && isCurrentUser && contactRequest != null) {
|
if (contact != null && isCurrentUser && contactRequest != null) {
|
||||||
val chat = Chat(remoteHostId = rhId, ChatInfo.Direct(contact), listOf())
|
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) {
|
fun rejectContactRequest(rhId: Long?, contactRequest: ChatInfo.ContactRequest, chatModel: ChatModel) {
|
||||||
withApi {
|
withBGApi {
|
||||||
chatModel.controller.apiRejectContactRequest(rhId, contactRequest.apiId)
|
chatModel.controller.apiRejectContactRequest(rhId, contactRequest.apiId)
|
||||||
chatModel.removeChat(rhId, contactRequest.id)
|
chatModel.removeChat(rhId, contactRequest.id)
|
||||||
}
|
}
|
||||||
@ -606,7 +606,7 @@ fun deleteContactConnectionAlert(rhId: Long?, connection: PendingContactConnecti
|
|||||||
),
|
),
|
||||||
confirmText = generalGetString(MR.strings.delete_verb),
|
confirmText = generalGetString(MR.strings.delete_verb),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
withApi {
|
withBGApi {
|
||||||
AlertManager.shared.hideAlert()
|
AlertManager.shared.hideAlert()
|
||||||
if (chatModel.controller.apiDeleteChat(rhId, ChatType.ContactConnection, connection.apiId)) {
|
if (chatModel.controller.apiDeleteChat(rhId, ChatType.ContactConnection, connection.apiId)) {
|
||||||
chatModel.removeChat(rhId, connection.id)
|
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),
|
text = generalGetString(MR.strings.alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry),
|
||||||
confirmText = generalGetString(MR.strings.button_delete_contact),
|
confirmText = generalGetString(MR.strings.button_delete_contact),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiDeleteChat(rhId, chatInfo.chatType, chatInfo.apiId)
|
val r = chatModel.controller.apiDeleteChat(rhId, chatInfo.chatType, chatInfo.apiId)
|
||||||
if (r) {
|
if (r) {
|
||||||
chatModel.removeChat(rhId, chatInfo.id)
|
chatModel.removeChat(rhId, chatInfo.id)
|
||||||
@ -654,7 +654,7 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress(
|
|||||||
Column {
|
Column {
|
||||||
SectionItemView({
|
SectionItemView({
|
||||||
AlertManager.privacySensitive.hideAlert()
|
AlertManager.privacySensitive.hideAlert()
|
||||||
withApi {
|
withBGApi {
|
||||||
close?.invoke()
|
close?.invoke()
|
||||||
val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = false)
|
val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = false)
|
||||||
if (ok && openChat) {
|
if (ok && openChat) {
|
||||||
@ -666,7 +666,7 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress(
|
|||||||
}
|
}
|
||||||
SectionItemView({
|
SectionItemView({
|
||||||
AlertManager.privacySensitive.hideAlert()
|
AlertManager.privacySensitive.hideAlert()
|
||||||
withApi {
|
withBGApi {
|
||||||
close?.invoke()
|
close?.invoke()
|
||||||
val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = true)
|
val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = true)
|
||||||
if (ok && openChat) {
|
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),
|
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),
|
confirmText = if (groupInfo.membership.memberIncognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
withApi {
|
withBGApi {
|
||||||
inProgress?.value = true
|
inProgress?.value = true
|
||||||
chatModel.controller.apiJoinGroup(rhId, groupInfo.groupId)
|
chatModel.controller.apiJoinGroup(rhId, groupInfo.groupId)
|
||||||
inProgress?.value = false
|
inProgress?.value = false
|
||||||
@ -728,7 +728,7 @@ fun cantInviteIncognitoAlert() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun deleteGroup(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) {
|
fun deleteGroup(rhId: Long?, groupInfo: GroupInfo, chatModel: ChatModel) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val r = chatModel.controller.apiDeleteChat(rhId, ChatType.Group, groupInfo.apiId)
|
val r = chatModel.controller.apiDeleteChat(rhId, ChatType.Group, groupInfo.apiId)
|
||||||
if (r) {
|
if (r) {
|
||||||
chatModel.removeChat(rhId, groupInfo.id)
|
chatModel.removeChat(rhId, groupInfo.id)
|
||||||
@ -769,7 +769,7 @@ fun updateChatSettings(chat: Chat, chatSettings: ChatSettings, chatModel: ChatMo
|
|||||||
}
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
withApi {
|
withBGApi {
|
||||||
val res = when (newChatInfo) {
|
val res = when (newChatInfo) {
|
||||||
is ChatInfo.Direct -> with(newChatInfo) {
|
is ChatInfo.Direct -> with(newChatInfo) {
|
||||||
chatModel.controller.apiSetSettings(chat.remoteHostId, chatType, apiId, contact.chatSettings)
|
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) {
|
if (chatModel.currentUser.value == null) {
|
||||||
chatModel.appOpenUrl.value = rhId to uri
|
chatModel.appOpenUrl.value = rhId to uri
|
||||||
} else {
|
} else {
|
||||||
withApi {
|
withBGApi {
|
||||||
planAndConnect(rhId, uri, incognito = null, close = null)
|
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.common.views.usersettings.doWithAuth
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -117,15 +117,17 @@ fun UserPicker(
|
|||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
// Controller.ctrl can be null when self-destructing activates
|
// Controller.ctrl can be null when self-destructing activates
|
||||||
if (controller.ctrl != null && controller.ctrl != -1L) {
|
if (controller.ctrl != null && controller.ctrl != -1L) {
|
||||||
|
withBGApi {
|
||||||
controller.reloadRemoteHosts()
|
controller.reloadRemoteHosts()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
val UsersView: @Composable ColumnScope.() -> Unit = {
|
val UsersView: @Composable ColumnScope.() -> Unit = {
|
||||||
users.forEach { u ->
|
users.forEach { u ->
|
||||||
UserProfilePickerItem(u.user, u.unreadCount, openSettings = settingsClicked) {
|
UserProfilePickerItem(u.user, u.unreadCount, openSettings = settingsClicked) {
|
||||||
userPickerState.value = AnimatedViewState.HIDING
|
userPickerState.value = AnimatedViewState.HIDING
|
||||||
if (!u.user.activeUser) {
|
if (!u.user.activeUser) {
|
||||||
scope.launch {
|
withBGApi {
|
||||||
controller.showProgressIfNeeded {
|
controller.showProgressIfNeeded {
|
||||||
ModalManager.closeAllModalsEverywhere()
|
ModalManager.closeAllModalsEverywhere()
|
||||||
chatModel.controller.changeActiveUser(u.user.remoteHostId, u.user.userId, null)
|
chatModel.controller.changeActiveUser(u.user.remoteHostId, u.user.userId, null)
|
||||||
|
@ -34,7 +34,7 @@ fun ChatArchiveView(m: ChatModel, title: String, archiveName: String, archiveTim
|
|||||||
ChatArchiveLayout(
|
ChatArchiveLayout(
|
||||||
title,
|
title,
|
||||||
archiveTime,
|
archiveTime,
|
||||||
saveArchive = { withApi { saveArchiveLauncher.launch(archivePath.substringAfterLast(File.separator)) }},
|
saveArchive = { withBGApi { saveArchiveLauncher.launch(archivePath.substringAfterLast(File.separator)) }},
|
||||||
deleteArchiveAlert = { deleteArchiveAlert(m, archivePath) }
|
deleteArchiveAlert = { deleteArchiveAlert(m, archivePath) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@ fun DatabaseEncryptionView(m: ChatModel) {
|
|||||||
initialRandomDBPassphrase,
|
initialRandomDBPassphrase,
|
||||||
progressIndicator,
|
progressIndicator,
|
||||||
onConfirmEncrypt = {
|
onConfirmEncrypt = {
|
||||||
withApi {
|
withBGApi {
|
||||||
encryptDatabase(currentKey, newKey, confirmNewKey, initialRandomDBPassphrase, useKeychain, storedKey, progressIndicator)
|
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.views.usersettings.*
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.datetime.*
|
import kotlinx.datetime.*
|
||||||
import java.io.*
|
import java.io.*
|
||||||
@ -88,8 +89,8 @@ fun DatabaseView(
|
|||||||
chatItemTTL,
|
chatItemTTL,
|
||||||
user,
|
user,
|
||||||
m.users,
|
m.users,
|
||||||
startChat = { startChat(m, chatLastStart, m.chatDbChanged) },
|
startChat = { startChat(m, chatLastStart, m.chatDbChanged, progressIndicator) },
|
||||||
stopChatAlert = { stopChatAlert(m) },
|
stopChatAlert = { stopChatAlert(m, progressIndicator) },
|
||||||
exportArchive = { exportArchive(m, progressIndicator, chatArchiveName, chatArchiveTime, chatArchiveFile, saveArchiveLauncher) },
|
exportArchive = { exportArchive(m, progressIndicator, chatArchiveName, chatArchiveTime, chatArchiveFile, saveArchiveLauncher) },
|
||||||
deleteChatAlert = { deleteChatAlert(m, progressIndicator) },
|
deleteChatAlert = { deleteChatAlert(m, progressIndicator) },
|
||||||
deleteAppFilesAndMedia = { deleteFilesAndMediaAlert(appFilesCountAndSize) },
|
deleteAppFilesAndMedia = { deleteFilesAndMediaAlert(appFilesCountAndSize) },
|
||||||
@ -187,7 +188,7 @@ fun DatabaseLayout(
|
|||||||
Text(generalGetString(MR.strings.disconnect_remote_hosts), Modifier.fillMaxWidth(), color = WarningOrange)
|
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(
|
SectionTextFooter(
|
||||||
if (stopped) {
|
if (stopped) {
|
||||||
@ -239,7 +240,7 @@ fun DatabaseLayout(
|
|||||||
SettingsActionItem(
|
SettingsActionItem(
|
||||||
painterResource(MR.images.ic_download),
|
painterResource(MR.images.ic_download),
|
||||||
stringResource(MR.strings.import_database),
|
stringResource(MR.strings.import_database),
|
||||||
{ withApi { importArchiveLauncher.launch("application/zip") } },
|
{ withBGApi { importArchiveLauncher.launch("application/zip") } },
|
||||||
textColor = Color.Red,
|
textColor = Color.Red,
|
||||||
iconColor = Color.Red,
|
iconColor = Color.Red,
|
||||||
disabled = operationsDisabled
|
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)
|
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>) {
|
fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged: MutableState<Boolean>, progressIndicator: MutableState<Boolean>? = null) {
|
||||||
withApi {
|
withBGApi {
|
||||||
try {
|
try {
|
||||||
|
progressIndicator?.value = true
|
||||||
if (chatDbChanged.value) {
|
if (chatDbChanged.value) {
|
||||||
initChatController()
|
initChatController()
|
||||||
chatDbChanged.value = false
|
chatDbChanged.value = false
|
||||||
@ -376,12 +378,12 @@ fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged
|
|||||||
if (m.chatDbStatus.value !is DBMigrationResult.OK) {
|
if (m.chatDbStatus.value !is DBMigrationResult.OK) {
|
||||||
/** Hide current view and show [DatabaseErrorView] */
|
/** Hide current view and show [DatabaseErrorView] */
|
||||||
ModalManager.closeAllModalsEverywhere()
|
ModalManager.closeAllModalsEverywhere()
|
||||||
return@withApi
|
return@withBGApi
|
||||||
}
|
}
|
||||||
val user = m.currentUser.value
|
val user = m.currentUser.value
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
ModalManager.closeAllModalsEverywhere()
|
ModalManager.closeAllModalsEverywhere()
|
||||||
return@withApi
|
return@withBGApi
|
||||||
} else {
|
} else {
|
||||||
m.controller.startChat(user)
|
m.controller.startChat(user)
|
||||||
}
|
}
|
||||||
@ -392,16 +394,18 @@ fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged
|
|||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
m.chatRunning.value = false
|
m.chatRunning.value = false
|
||||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_starting_chat), e.toString())
|
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(
|
AlertManager.shared.showAlertDialog(
|
||||||
title = generalGetString(MR.strings.stop_chat_question),
|
title = generalGetString(MR.strings.stop_chat_question),
|
||||||
text = generalGetString(MR.strings.stop_chat_to_export_import_or_delete_chat_database),
|
text = generalGetString(MR.strings.stop_chat_to_export_import_or_delete_chat_database),
|
||||||
confirmText = generalGetString(MR.strings.stop_chat_confirmation),
|
confirmText = generalGetString(MR.strings.stop_chat_confirmation),
|
||||||
onConfirm = { authStopChat(m) },
|
onConfirm = { authStopChat(m, progressIndicator = progressIndicator) },
|
||||||
onDismiss = { m.chatRunning.value = true }
|
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()) {
|
if (m.controller.appPrefs.performLA.get()) {
|
||||||
authenticate(
|
authenticate(
|
||||||
generalGetString(MR.strings.auth_stop_chat),
|
generalGetString(MR.strings.auth_stop_chat),
|
||||||
@ -423,7 +427,7 @@ fun authStopChat(m: ChatModel, onStop: (() -> Unit)? = null) {
|
|||||||
completed = { laResult ->
|
completed = { laResult ->
|
||||||
when (laResult) {
|
when (laResult) {
|
||||||
LAResult.Success, is LAResult.Unavailable -> {
|
LAResult.Success, is LAResult.Unavailable -> {
|
||||||
stopChat(m, onStop)
|
stopChat(m, progressIndicator, onStop)
|
||||||
}
|
}
|
||||||
is LAResult.Error -> {
|
is LAResult.Error -> {
|
||||||
m.chatRunning.value = true
|
m.chatRunning.value = true
|
||||||
@ -436,19 +440,22 @@ fun authStopChat(m: ChatModel, onStop: (() -> Unit)? = null) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
stopChat(m, onStop)
|
stopChat(m, progressIndicator, onStop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopChat(m: ChatModel, onStop: (() -> Unit)? = null) {
|
private fun stopChat(m: ChatModel, progressIndicator: MutableState<Boolean>? = null, onStop: (() -> Unit)? = null) {
|
||||||
withApi {
|
withBGApi {
|
||||||
try {
|
try {
|
||||||
|
progressIndicator?.value = true
|
||||||
stopChatAsync(m)
|
stopChatAsync(m)
|
||||||
platform.androidChatStopped()
|
platform.androidChatStopped()
|
||||||
onStop?.invoke()
|
onStop?.invoke()
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
m.chatRunning.value = true
|
m.chatRunning.value = true
|
||||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_stopping_chat), e.toString())
|
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_stopping_chat), e.toString())
|
||||||
|
} finally {
|
||||||
|
progressIndicator?.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -493,7 +500,7 @@ private fun exportArchive(
|
|||||||
saveArchiveLauncher: FileChooserLauncher
|
saveArchiveLauncher: FileChooserLauncher
|
||||||
) {
|
) {
|
||||||
progressIndicator.value = true
|
progressIndicator.value = true
|
||||||
withApi {
|
withBGApi {
|
||||||
try {
|
try {
|
||||||
val archiveFile = exportChatArchive(m, chatArchiveName, chatArchiveTime, chatArchiveFile)
|
val archiveFile = exportChatArchive(m, chatArchiveName, chatArchiveTime, chatArchiveFile)
|
||||||
chatArchiveFile.value = archiveFile
|
chatArchiveFile.value = archiveFile
|
||||||
@ -567,7 +574,7 @@ private fun importArchive(
|
|||||||
progressIndicator.value = true
|
progressIndicator.value = true
|
||||||
val archivePath = saveArchiveFromURI(importedArchiveURI)
|
val archivePath = saveArchiveFromURI(importedArchiveURI)
|
||||||
if (archivePath != null) {
|
if (archivePath != null) {
|
||||||
withApi {
|
withBGApi {
|
||||||
try {
|
try {
|
||||||
m.controller.apiDeleteStorage()
|
m.controller.apiDeleteStorage()
|
||||||
try {
|
try {
|
||||||
@ -635,7 +642,7 @@ private fun deleteChatAlert(m: ChatModel, progressIndicator: MutableState<Boolea
|
|||||||
|
|
||||||
private fun deleteChat(m: ChatModel, progressIndicator: MutableState<Boolean>) {
|
private fun deleteChat(m: ChatModel, progressIndicator: MutableState<Boolean>) {
|
||||||
progressIndicator.value = true
|
progressIndicator.value = true
|
||||||
withApi {
|
withBGApi {
|
||||||
try {
|
try {
|
||||||
deleteChatAsync(m)
|
deleteChatAsync(m)
|
||||||
operationEnded(m, progressIndicator) {
|
operationEnded(m, progressIndicator) {
|
||||||
@ -658,7 +665,7 @@ private fun setCiTTL(
|
|||||||
) {
|
) {
|
||||||
Log.d(TAG, "DatabaseView setChatItemTTL ${chatItemTTL.value.seconds ?: -1}")
|
Log.d(TAG, "DatabaseView setChatItemTTL ${chatItemTTL.value.seconds ?: -1}")
|
||||||
progressIndicator.value = true
|
progressIndicator.value = true
|
||||||
withApi {
|
withBGApi {
|
||||||
try {
|
try {
|
||||||
m.controller.setChatItemTTL(rhId, chatItemTTL.value)
|
m.controller.setChatItemTTL(rhId, chatItemTTL.value)
|
||||||
// Update model on success
|
// Update model on success
|
||||||
|
@ -89,7 +89,7 @@ enum class MigrationConfirmation(val value: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun defaultMigrationConfirmation(appPrefs: AppPreferences): MigrationConfirmation =
|
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
|
@Serializable
|
||||||
sealed class MigrationError {
|
sealed class MigrationError {
|
||||||
|
@ -45,7 +45,7 @@ class ModalData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ModalManager(private val placement: ModalPlacement? = null) {
|
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 modalCount = mutableStateOf(0)
|
||||||
private val toRemove = mutableSetOf<Int>()
|
private val toRemove = mutableSetOf<Int>()
|
||||||
private var oldViewChanging = AtomicBoolean(false)
|
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")
|
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.
|
// 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
|
// This is useful when invoking close() and ShowCustomModal one after another without delay. Otherwise, screen will hold prev view
|
||||||
if (toRemove.isNotEmpty()) {
|
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)
|
// 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
|
// to prevent unneeded animation on different situations
|
||||||
val anim = if (appPlatform.isAndroid) animated else animated && (modalCount.value > 0 || placement == ModalPlacement.START)
|
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
|
modalCount.value = modalViews.size - toRemove.size
|
||||||
|
|
||||||
if (placement == ModalPlacement.CENTER) {
|
if (placement == ModalPlacement.CENTER) {
|
||||||
@ -117,7 +118,7 @@ class ModalManager(private val placement: ModalPlacement? = null) {
|
|||||||
fun showInView() {
|
fun showInView() {
|
||||||
// Without animation
|
// Without animation
|
||||||
if (modalCount.value > 0 && modalViews.lastOrNull()?.first == false) {
|
if (modalCount.value > 0 && modalViews.lastOrNull()?.first == false) {
|
||||||
modalViews.lastOrNull()?.second?.invoke(::closeModal)
|
modalViews.lastOrNull()?.let { it.third(it.second, ::closeModal) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
AnimatedContent(targetState = modalCount.value,
|
AnimatedContent(targetState = modalCount.value,
|
||||||
@ -129,7 +130,7 @@ class ModalManager(private val placement: ModalPlacement? = null) {
|
|||||||
}.using(SizeTransform(clip = false))
|
}.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
|
// 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) {
|
if (toRemove.isNotEmpty() && it == modalCount.value && transition.currentState == EnterExitState.Visible && !transition.isRunning) {
|
||||||
runAtomically { toRemove.removeIf { elem -> modalViews.removeAt(elem); true } }
|
runAtomically { toRemove.removeIf { elem -> modalViews.removeAt(elem); true } }
|
||||||
|
@ -43,9 +43,8 @@ class ProcessedErrors <T: AgentErrorType>(val interval: Long) {
|
|||||||
title = title,
|
title = title,
|
||||||
text = text,
|
text = text,
|
||||||
confirmText = generalGetString(MR.strings.restart_chat_button),
|
confirmText = generalGetString(MR.strings.restart_chat_button),
|
||||||
onConfirm = {
|
onConfirm = ::restartChatOrApp
|
||||||
withApi { restartChatOrApp() }
|
)
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
AlertManager.shared.showAlertMsg(
|
AlertManager.shared.showAlertMsg(
|
||||||
title = title,
|
title = title,
|
||||||
|
@ -2,6 +2,7 @@ package chat.simplex.common.views.helpers
|
|||||||
|
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.runtime.saveable.Saver
|
import androidx.compose.runtime.saveable.Saver
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.graphics.*
|
import androidx.compose.ui.graphics.*
|
||||||
import androidx.compose.ui.platform.*
|
import androidx.compose.ui.platform.*
|
||||||
import androidx.compose.ui.text.*
|
import androidx.compose.ui.text.*
|
||||||
@ -21,15 +22,71 @@ import java.net.URI
|
|||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import kotlin.math.*
|
import kotlin.math.*
|
||||||
|
|
||||||
|
private val singleThreadDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
|
||||||
|
|
||||||
fun withApi(action: suspend CoroutineScope.() -> Unit): Job = withScope(GlobalScope, action)
|
fun withApi(action: suspend CoroutineScope.() -> Unit): Job = withScope(GlobalScope, action)
|
||||||
|
|
||||||
fun withScope(scope: CoroutineScope, action: suspend CoroutineScope.() -> Unit): Job =
|
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 =
|
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 {
|
enum class KeyboardState {
|
||||||
Opened, Closed
|
Opened, Closed
|
||||||
@ -422,11 +479,15 @@ fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {}
|
|||||||
val orientation = windowOrientation()
|
val orientation = windowOrientation()
|
||||||
onDispose {
|
onDispose {
|
||||||
whenDispose()
|
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()) {
|
if (orientation == windowOrientation()) {
|
||||||
whenGone()
|
whenGone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -36,7 +36,7 @@ fun AddGroupView(chatModel: ChatModel, rh: RemoteHostInfo?, close: () -> Unit) {
|
|||||||
val rhId = rh?.remoteHostId
|
val rhId = rh?.remoteHostId
|
||||||
AddGroupLayout(
|
AddGroupLayout(
|
||||||
createGroup = { incognito, groupProfile ->
|
createGroup = { incognito, groupProfile ->
|
||||||
withApi {
|
withBGApi {
|
||||||
val groupInfo = chatModel.controller.apiNewGroup(rhId, incognito, groupProfile)
|
val groupInfo = chatModel.controller.apiNewGroup(rhId, incognito, groupProfile)
|
||||||
if (groupInfo != null) {
|
if (groupInfo != null) {
|
||||||
chatModel.addChat(Chat(remoteHostId = rhId, chatInfo = ChatInfo.Group(groupInfo), chatItems = listOf()))
|
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),
|
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link) + linkText,
|
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),
|
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,
|
onDismiss = cleanup,
|
||||||
onDismissRequest = cleanup,
|
onDismissRequest = cleanup,
|
||||||
destructive = true,
|
destructive = true,
|
||||||
@ -134,7 +134,7 @@ suspend fun planAndConnect(
|
|||||||
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
|
||||||
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address) + linkText,
|
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),
|
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,
|
destructive = true,
|
||||||
onDismiss = cleanup,
|
onDismiss = cleanup,
|
||||||
onDismissRequest = cleanup,
|
onDismissRequest = cleanup,
|
||||||
@ -157,7 +157,7 @@ suspend fun planAndConnect(
|
|||||||
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
|
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
|
||||||
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address) + linkText,
|
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),
|
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,
|
onDismiss = cleanup,
|
||||||
onDismissRequest = cleanup,
|
onDismissRequest = cleanup,
|
||||||
destructive = true,
|
destructive = true,
|
||||||
@ -223,7 +223,7 @@ suspend fun planAndConnect(
|
|||||||
title = generalGetString(MR.strings.connect_via_group_link),
|
title = generalGetString(MR.strings.connect_via_group_link),
|
||||||
text = generalGetString(MR.strings.you_will_join_group) + linkText,
|
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),
|
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,
|
onDismiss = cleanup,
|
||||||
onDismissRequest = cleanup,
|
onDismissRequest = cleanup,
|
||||||
hostDevice = hostDevice(rhId),
|
hostDevice = hostDevice(rhId),
|
||||||
@ -254,7 +254,7 @@ suspend fun planAndConnect(
|
|||||||
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
|
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,
|
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),
|
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,
|
onDismiss = cleanup,
|
||||||
onDismissRequest = cleanup,
|
onDismissRequest = cleanup,
|
||||||
destructive = true,
|
destructive = true,
|
||||||
@ -374,7 +374,7 @@ fun askCurrentOrIncognitoProfileAlert(
|
|||||||
val connectColor = if (connectDestructive) MaterialTheme.colors.error else MaterialTheme.colors.primary
|
val connectColor = if (connectDestructive) MaterialTheme.colors.error else MaterialTheme.colors.primary
|
||||||
SectionItemView({
|
SectionItemView({
|
||||||
AlertManager.privacySensitive.hideAlert()
|
AlertManager.privacySensitive.hideAlert()
|
||||||
withApi {
|
withBGApi {
|
||||||
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup)
|
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
@ -382,7 +382,7 @@ fun askCurrentOrIncognitoProfileAlert(
|
|||||||
}
|
}
|
||||||
SectionItemView({
|
SectionItemView({
|
||||||
AlertManager.privacySensitive.hideAlert()
|
AlertManager.privacySensitive.hideAlert()
|
||||||
withApi {
|
withBGApi {
|
||||||
connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup)
|
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) {
|
fun openKnownContact(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, contact: Contact) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val c = chatModel.getContactChat(contact.contactId)
|
val c = chatModel.getContactChat(contact.contactId)
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
close?.invoke()
|
close?.invoke()
|
||||||
@ -439,7 +439,7 @@ fun ownGroupLinkConfirmConnect(
|
|||||||
// Join incognito / Join with current profile
|
// Join incognito / Join with current profile
|
||||||
SectionItemView({
|
SectionItemView({
|
||||||
AlertManager.privacySensitive.hideAlert()
|
AlertManager.privacySensitive.hideAlert()
|
||||||
withApi {
|
withBGApi {
|
||||||
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup)
|
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close, cleanup)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
@ -452,7 +452,7 @@ fun ownGroupLinkConfirmConnect(
|
|||||||
// Use current profile
|
// Use current profile
|
||||||
SectionItemView({
|
SectionItemView({
|
||||||
AlertManager.privacySensitive.hideAlert()
|
AlertManager.privacySensitive.hideAlert()
|
||||||
withApi {
|
withBGApi {
|
||||||
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup)
|
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close, cleanup)
|
||||||
}
|
}
|
||||||
}) {
|
}) {
|
||||||
@ -461,7 +461,7 @@ fun ownGroupLinkConfirmConnect(
|
|||||||
// Use new incognito profile
|
// Use new incognito profile
|
||||||
SectionItemView({
|
SectionItemView({
|
||||||
AlertManager.privacySensitive.hideAlert()
|
AlertManager.privacySensitive.hideAlert()
|
||||||
withApi {
|
withBGApi {
|
||||||
connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close, cleanup)
|
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) {
|
fun openKnownGroup(chatModel: ChatModel, rhId: Long?, close: (() -> Unit)?, groupInfo: GroupInfo) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val g = chatModel.getGroupChat(groupInfo.groupId)
|
val g = chatModel.getGroupChat(groupInfo.groupId)
|
||||||
if (g != null) {
|
if (g != null) {
|
||||||
close?.invoke()
|
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.controller.apiSetConnectionAlias(rhId, contactConnection.pccConnId, localAlias)?.let {
|
||||||
chatModel.updateContactConnection(rhId, it)
|
chatModel.updateContactConnection(rhId, it)
|
||||||
}
|
}
|
||||||
|
@ -371,7 +371,7 @@ private fun createInvitation(
|
|||||||
) {
|
) {
|
||||||
if (connReqInvitation.isNotEmpty() || contactConnection.value != null || creatingConnReq.value) return
|
if (connReqInvitation.isNotEmpty() || contactConnection.value != null || creatingConnReq.value) return
|
||||||
creatingConnReq.value = true
|
creatingConnReq.value = true
|
||||||
withApi {
|
withBGApi {
|
||||||
val (r, alert) = controller.apiAddContact(rhId, incognito = controller.appPrefs.incognito.get())
|
val (r, alert) = controller.apiAddContact(rhId, incognito = controller.appPrefs.incognito.get())
|
||||||
if (r != null) {
|
if (r != null) {
|
||||||
chatModel.updateContactConnection(rhId, r.second)
|
chatModel.updateContactConnection(rhId, r.second)
|
||||||
|
@ -43,7 +43,7 @@ fun CreateSimpleXAddress(m: ChatModel, rhId: Long?) {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
createAddress = {
|
createAddress = {
|
||||||
withApi {
|
withBGApi {
|
||||||
progressIndicator = true
|
progressIndicator = true
|
||||||
val connReqContact = m.controller.apiCreateUserAddress(rhId)
|
val connReqContact = m.controller.apiCreateUserAddress(rhId)
|
||||||
if (connReqContact != null) {
|
if (connReqContact != null) {
|
||||||
@ -170,8 +170,8 @@ private fun ProgressIndicator() {
|
|||||||
|
|
||||||
private fun prepareChatBeforeAddressCreation(rhId: Long?) {
|
private fun prepareChatBeforeAddressCreation(rhId: Long?) {
|
||||||
if (chatModel.users.isNotEmpty()) return
|
if (chatModel.users.isNotEmpty()) return
|
||||||
withApi {
|
withBGApi {
|
||||||
val user = chatModel.controller.apiGetActiveUser(rhId) ?: return@withApi
|
val user = chatModel.controller.apiGetActiveUser(rhId) ?: return@withBGApi
|
||||||
chatModel.currentUser.value = user
|
chatModel.currentUser.value = user
|
||||||
if (chatModel.users.isEmpty()) {
|
if (chatModel.users.isEmpty()) {
|
||||||
if (appPlatform.isDesktop) {
|
if (appPlatform.isDesktop) {
|
||||||
|
@ -50,7 +50,7 @@ fun SetupDatabasePassphrase(m: ChatModel) {
|
|||||||
confirmNewKey,
|
confirmNewKey,
|
||||||
progressIndicator,
|
progressIndicator,
|
||||||
onConfirmEncrypt = {
|
onConfirmEncrypt = {
|
||||||
withApi {
|
withBGApi {
|
||||||
if (m.chatRunning.value == true) {
|
if (m.chatRunning.value == true) {
|
||||||
// Stop chat if it's started before doing anything
|
// Stop chat if it's started before doing anything
|
||||||
stopChatAsync(m)
|
stopChatAsync(m)
|
||||||
|
@ -38,7 +38,9 @@ import chat.simplex.common.views.usersettings.*
|
|||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ConnectMobileView() {
|
fun ConnectMobileView() {
|
||||||
@ -46,8 +48,10 @@ fun ConnectMobileView() {
|
|||||||
val remoteHosts = remember { chatModel.remoteHosts }
|
val remoteHosts = remember { chatModel.remoteHosts }
|
||||||
val deviceName = chatModel.controller.appPrefs.deviceNameForRemoteAccess
|
val deviceName = chatModel.controller.appPrefs.deviceNameForRemoteAccess
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
withBGApi {
|
||||||
controller.reloadRemoteHosts()
|
controller.reloadRemoteHosts()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ConnectMobileLayout(
|
ConnectMobileLayout(
|
||||||
deviceName = remember { deviceName.state },
|
deviceName = remember { deviceName.state },
|
||||||
remoteHosts = remoteHosts,
|
remoteHosts = remoteHosts,
|
||||||
|
@ -92,7 +92,7 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun saveCfg(cfg: NetCfg) {
|
fun saveCfg(cfg: NetCfg) {
|
||||||
withApi {
|
withBGApi {
|
||||||
chatModel.controller.apiSetNetworkConfig(cfg)
|
chatModel.controller.apiSetNetworkConfig(cfg)
|
||||||
currentCfg.value = cfg
|
currentCfg.value = cfg
|
||||||
chatModel.controller.setNetCfg(cfg)
|
chatModel.controller.setNetCfg(cfg)
|
||||||
|
@ -131,7 +131,7 @@ object AppearanceScope {
|
|||||||
SectionItemView({
|
SectionItemView({
|
||||||
val overrides = ThemeManager.currentThemeOverridesForExport(isInDarkTheme)
|
val overrides = ThemeManager.currentThemeOverridesForExport(isInDarkTheme)
|
||||||
theme.value = yaml.encodeToString<ThemeOverrides>(overrides)
|
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)
|
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
|
// 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)
|
Text(generalGetString(MR.strings.import_theme), color = colors.primary)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package chat.simplex.common.views.usersettings
|
package chat.simplex.common.views.usersettings
|
||||||
|
|
||||||
import SectionBottomSpacer
|
import SectionBottomSpacer
|
||||||
|
import SectionSpacer
|
||||||
import SectionTextFooter
|
import SectionTextFooter
|
||||||
import SectionView
|
import SectionView
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -22,7 +23,7 @@ import chat.simplex.res.MR
|
|||||||
@Composable
|
@Composable
|
||||||
fun DeveloperView(
|
fun DeveloperView(
|
||||||
m: ChatModel,
|
m: ChatModel,
|
||||||
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
||||||
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
|
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
|
||||||
) {
|
) {
|
||||||
Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) {
|
Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) {
|
||||||
@ -30,12 +31,20 @@ fun DeveloperView(
|
|||||||
AppBarTitle(stringResource(MR.strings.settings_developer_tools))
|
AppBarTitle(stringResource(MR.strings.settings_developer_tools))
|
||||||
val developerTools = m.controller.appPrefs.developerTools
|
val developerTools = m.controller.appPrefs.developerTools
|
||||||
val devTools = remember { developerTools.state }
|
val devTools = remember { developerTools.state }
|
||||||
SectionView() {
|
SectionView {
|
||||||
InstallTerminalAppItem(uriHandler)
|
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) })}
|
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)
|
|
||||||
SettingsPreferenceItem(painterResource(MR.images.ic_code), stringResource(MR.strings.show_developer_options), developerTools)
|
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 ->
|
TerminalAlwaysVisibleItem(m.controller.appPrefs.terminalAlwaysVisible) { checked ->
|
||||||
if (checked) {
|
if (checked) {
|
||||||
withAuth(generalGetString(MR.strings.auth_open_chat_console), generalGetString(MR.strings.auth_log_in_using_credential)) {
|
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)
|
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_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()
|
SectionBottomSpacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ fun NetworkAndServersView(
|
|||||||
chatModel: ChatModel,
|
chatModel: ChatModel,
|
||||||
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
showSettingsModal: (@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 }
|
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
|
// 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),
|
text = generalGetString(MR.strings.network_enable_socks_info).format(proxyPort.value),
|
||||||
confirmText = generalGetString(MR.strings.confirm_verb),
|
confirmText = generalGetString(MR.strings.confirm_verb),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val conf = NetCfg.proxyDefaults.withHostPort(chatModel.controller.appPrefs.networkProxyHostPort.get())
|
val conf = NetCfg.proxyDefaults.withHostPort(chatModel.controller.appPrefs.networkProxyHostPort.get())
|
||||||
chatModel.controller.apiSetNetworkConfig(conf)
|
chatModel.controller.apiSetNetworkConfig(conf)
|
||||||
chatModel.controller.setNetCfg(conf)
|
chatModel.controller.setNetCfg(conf)
|
||||||
@ -84,7 +84,7 @@ fun NetworkAndServersView(
|
|||||||
text = generalGetString(MR.strings.network_disable_socks_info),
|
text = generalGetString(MR.strings.network_disable_socks_info),
|
||||||
confirmText = generalGetString(MR.strings.confirm_verb),
|
confirmText = generalGetString(MR.strings.confirm_verb),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val conf = NetCfg.defaults
|
val conf = NetCfg.defaults
|
||||||
chatModel.controller.apiSetNetworkConfig(conf)
|
chatModel.controller.apiSetNetworkConfig(conf)
|
||||||
chatModel.controller.setNetCfg(conf)
|
chatModel.controller.setNetCfg(conf)
|
||||||
@ -111,7 +111,7 @@ fun NetworkAndServersView(
|
|||||||
onionHosts.value = prevValue
|
onionHosts.value = prevValue
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val newCfg = chatModel.controller.getNetCfg().withOnionHosts(it)
|
val newCfg = chatModel.controller.getNetCfg().withOnionHosts(it)
|
||||||
val res = chatModel.controller.apiSetNetworkConfig(newCfg)
|
val res = chatModel.controller.apiSetNetworkConfig(newCfg)
|
||||||
if (res) {
|
if (res) {
|
||||||
@ -136,7 +136,7 @@ fun NetworkAndServersView(
|
|||||||
startsWith,
|
startsWith,
|
||||||
onDismiss = { sessionMode.value = prevValue }
|
onDismiss = { sessionMode.value = prevValue }
|
||||||
) {
|
) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val newCfg = chatModel.controller.getNetCfg().copy(sessionMode = it)
|
val newCfg = chatModel.controller.getNetCfg().copy(sessionMode = it)
|
||||||
val res = chatModel.controller.apiSetNetworkConfig(newCfg)
|
val res = chatModel.controller.apiSetNetworkConfig(newCfg)
|
||||||
if (res) {
|
if (res) {
|
||||||
@ -160,7 +160,7 @@ fun NetworkAndServersView(
|
|||||||
proxyPort: State<Int>,
|
proxyPort: State<Int>,
|
||||||
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
showSettingsModal: (@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,
|
toggleSocksProxy: (Boolean) -> Unit,
|
||||||
useOnion: (OnionHosts) -> Unit,
|
useOnion: (OnionHosts) -> Unit,
|
||||||
updateSessionMode: (TransportSessionMode) -> Unit,
|
updateSessionMode: (TransportSessionMode) -> Unit,
|
||||||
|
@ -26,7 +26,7 @@ fun PreferencesView(m: ChatModel, user: User, close: () -> Unit,) {
|
|||||||
close()
|
close()
|
||||||
}
|
}
|
||||||
fun savePrefs(afterSave: () -> Unit = {}) {
|
fun savePrefs(afterSave: () -> Unit = {}) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val newProfile = user.profile.toProfile().copy(preferences = preferences.toPreferences())
|
val newProfile = user.profile.toProfile().copy(preferences = preferences.toPreferences())
|
||||||
val updated = m.controller.apiUpdateProfile(user.remoteHostId, newProfile)
|
val updated = m.controller.apiUpdateProfile(user.remoteHostId, newProfile)
|
||||||
if (updated != null) {
|
if (updated != null) {
|
||||||
|
@ -96,7 +96,7 @@ fun PrivacySettingsView(
|
|||||||
val currentUser = chatModel.currentUser.value
|
val currentUser = chatModel.currentUser.value
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
fun setSendReceiptsContacts(enable: Boolean, clearOverrides: Boolean) {
|
fun setSendReceiptsContacts(enable: Boolean, clearOverrides: Boolean) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val mrs = UserMsgReceiptSettings(enable, clearOverrides)
|
val mrs = UserMsgReceiptSettings(enable, clearOverrides)
|
||||||
chatModel.controller.apiSetUserContactReceipts(currentUser, mrs)
|
chatModel.controller.apiSetUserContactReceipts(currentUser, mrs)
|
||||||
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
||||||
@ -119,7 +119,7 @@ fun PrivacySettingsView(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setSendReceiptsGroups(enable: Boolean, clearOverrides: Boolean) {
|
fun setSendReceiptsGroups(enable: Boolean, clearOverrides: Boolean) {
|
||||||
withApi {
|
withBGApi {
|
||||||
val mrs = UserMsgReceiptSettings(enable, clearOverrides)
|
val mrs = UserMsgReceiptSettings(enable, clearOverrides)
|
||||||
chatModel.controller.apiSetUserGroupReceipts(currentUser, mrs)
|
chatModel.controller.apiSetUserGroupReceipts(currentUser, mrs)
|
||||||
chatModel.controller.appPrefs.privacyDeliveryReceiptsSet.set(true)
|
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.views.newchat.QRCode
|
||||||
import chat.simplex.common.model.ChatModel
|
import chat.simplex.common.model.ChatModel
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ProtocolServerView(m: ChatModel, server: ServerCfg, serverProtocol: ServerProtocol, onUpdate: (ServerCfg) -> Unit, onDelete: () -> Unit) {
|
fun ProtocolServerView(m: ChatModel, server: ServerCfg, serverProtocol: ServerProtocol, onUpdate: (ServerCfg) -> Unit, onDelete: () -> Unit) {
|
||||||
var testing by remember { mutableStateOf(false) }
|
var testing by remember { mutableStateOf(false) }
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
ProtocolServerLayout(
|
ProtocolServerLayout(
|
||||||
testing,
|
testing,
|
||||||
server,
|
server,
|
||||||
serverProtocol,
|
serverProtocol,
|
||||||
testServer = {
|
testServer = {
|
||||||
testing = true
|
testing = true
|
||||||
scope.launch {
|
withLongRunningApi {
|
||||||
val res = testServerConnection(server, m)
|
val res = testServerConnection(server, m)
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
onUpdate(res.first)
|
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.views.helpers.*
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.platform.appPlatform
|
import chat.simplex.common.platform.appPlatform
|
||||||
import chat.simplex.common.views.usersettings.ScanProtocolServer
|
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@Composable
|
@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 presetServers by remember(rhId) { mutableStateOf(emptyList<String>()) }
|
||||||
var servers by remember(rhId) {
|
var servers by remember(rhId) {
|
||||||
mutableStateOf(m.userSMPServersUnsaved.value ?: emptyList())
|
mutableStateOf(m.userSMPServersUnsaved.value ?: emptyList())
|
||||||
@ -56,6 +54,7 @@ fun ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtoco
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(rhId) {
|
LaunchedEffect(rhId) {
|
||||||
|
withApi {
|
||||||
val res = m.controller.getUserProtoServers(rhId, serverProtocol)
|
val res = m.controller.getUserProtoServers(rhId, serverProtocol)
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
currServers.value = res.protoServers
|
currServers.value = res.protoServers
|
||||||
@ -65,7 +64,8 @@ fun ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtoco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
val testServersJob = CancellableOnGoneJob()
|
||||||
fun showServer(server: ServerCfg) {
|
fun showServer(server: ServerCfg) {
|
||||||
ModalManager.start.showModalCloseable(true) { close ->
|
ModalManager.start.showModalCloseable(true) { close ->
|
||||||
var old by remember { mutableStateOf(server) }
|
var old by remember { mutableStateOf(server) }
|
||||||
@ -91,7 +91,6 @@ fun ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtoco
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
ModalView(
|
ModalView(
|
||||||
close = {
|
close = {
|
||||||
if (saveDisabled.value) close()
|
if (saveDisabled.value) close()
|
||||||
@ -148,7 +147,7 @@ fun ProtocolServersView(m: ChatModel, rhId: Long?, serverProtocol: ServerProtoco
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
testServers = {
|
testServers = {
|
||||||
scope.launch {
|
testServersJob.value = withLongRunningApi {
|
||||||
testServers(testing, servers, m) {
|
testServers(testing, servers, m) {
|
||||||
servers = it
|
servers = it
|
||||||
m.userSMPServersUnsaved.value = servers
|
m.userSMPServersUnsaved.value = servers
|
||||||
@ -338,6 +337,7 @@ private suspend fun runServersTest(servers: List<ServerCfg>, m: ChatModel, onUpd
|
|||||||
val updatedServers = ArrayList<ServerCfg>(servers)
|
val updatedServers = ArrayList<ServerCfg>(servers)
|
||||||
for ((index, server) in servers.withIndex()) {
|
for ((index, server) in servers.withIndex()) {
|
||||||
if (server.enabled) {
|
if (server.enabled) {
|
||||||
|
interruptIfCancelled()
|
||||||
val (updatedServer, f) = testServerConnection(server, m)
|
val (updatedServer, f) = testServerConnection(server, m)
|
||||||
updatedServers.removeAt(index)
|
updatedServers.removeAt(index)
|
||||||
updatedServers.add(index, updatedServer)
|
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 = {}) {
|
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)) {
|
if (m.controller.setUserProtoServers(rhId, protocol, servers)) {
|
||||||
currServers.value = servers
|
currServers.value = servers
|
||||||
m.userSMPServersUnsaved.value = null
|
m.userSMPServersUnsaved.value = null
|
||||||
|
@ -24,7 +24,7 @@ fun SetDeliveryReceiptsView(m: ChatModel) {
|
|||||||
enableReceipts = {
|
enableReceipts = {
|
||||||
val currentUser = m.currentUser.value
|
val currentUser = m.currentUser.value
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
withApi {
|
withBGApi {
|
||||||
try {
|
try {
|
||||||
m.controller.apiSetAllContactReceipts(currentUser.remoteHostId, enable = true)
|
m.controller.apiSetAllContactReceipts(currentUser.remoteHostId, enable = true)
|
||||||
m.currentUser.value = currentUser.copy(sendRcptsContacts = 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) } } },
|
showCustomModal = { modalView -> { ModalManager.start.showCustomModal { close -> modalView(chatModel, close) } } },
|
||||||
showVersion = {
|
showVersion = {
|
||||||
withApi {
|
withBGApi {
|
||||||
val info = chatModel.controller.apiGetVersion()
|
val info = chatModel.controller.apiGetVersion()
|
||||||
if (info != null) {
|
if (info != null) {
|
||||||
ModalManager.start.showModal { VersionInfoView(info) }
|
ModalManager.start.showModal { VersionInfoView(info) }
|
||||||
@ -89,7 +89,7 @@ fun SettingsLayout(
|
|||||||
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
showSettingsModalWithSearch: (@Composable (ChatModel, MutableState<String>) -> Unit) -> Unit,
|
showSettingsModalWithSearch: (@Composable (ChatModel, MutableState<String>) -> Unit) -> Unit,
|
||||||
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
||||||
showVersion: () -> Unit,
|
showVersion: () -> Unit,
|
||||||
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit,
|
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit,
|
||||||
drawerState: DrawerState,
|
drawerState: DrawerState,
|
||||||
@ -186,7 +186,7 @@ fun SettingsLayout(
|
|||||||
@Composable
|
@Composable
|
||||||
expect fun SettingsSectionApp(
|
expect fun SettingsSectionApp(
|
||||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
||||||
showVersion: () -> Unit,
|
showVersion: () -> Unit,
|
||||||
withAuth: (title: String, desc: String, block: () -> Unit) -> 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(
|
SettingsActionItem(
|
||||||
painterResource(MR.images.ic_toggle_on),
|
painterResource(MR.images.ic_toggle_on),
|
||||||
stringResource(MR.strings.chat_preferences),
|
stringResource(MR.strings.chat_preferences),
|
||||||
click = if (stopped) null else ({
|
click = if (stopped) null else ({
|
||||||
withApi {
|
|
||||||
showCustomModal { m, close ->
|
showCustomModal { m, close ->
|
||||||
PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close)
|
PreferencesView(m, m.currentUser.value ?: return@showCustomModal, close)
|
||||||
}()
|
}()
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
disabled = stopped,
|
disabled = stopped,
|
||||||
extraPadding = true
|
extraPadding = true
|
||||||
|
@ -70,7 +70,7 @@ fun UserAddressView(
|
|||||||
shareViaProfile,
|
shareViaProfile,
|
||||||
onCloseHandler,
|
onCloseHandler,
|
||||||
createAddress = {
|
createAddress = {
|
||||||
withApi {
|
withBGApi {
|
||||||
progressIndicator = true
|
progressIndicator = true
|
||||||
val connReqContact = chatModel.controller.apiCreateUserAddress(user?.value?.remoteHostId)
|
val connReqContact = chatModel.controller.apiCreateUserAddress(user?.value?.remoteHostId)
|
||||||
if (connReqContact != null) {
|
if (connReqContact != null) {
|
||||||
@ -116,7 +116,7 @@ fun UserAddressView(
|
|||||||
confirmText = generalGetString(MR.strings.delete_verb),
|
confirmText = generalGetString(MR.strings.delete_verb),
|
||||||
onConfirm = {
|
onConfirm = {
|
||||||
progressIndicator = true
|
progressIndicator = true
|
||||||
withApi {
|
withBGApi {
|
||||||
val u = chatModel.controller.apiDeleteUserAddress(user?.value?.remoteHostId)
|
val u = chatModel.controller.apiDeleteUserAddress(user?.value?.remoteHostId)
|
||||||
if (u != null) {
|
if (u != null) {
|
||||||
chatModel.userAddress.value = null
|
chatModel.userAddress.value = null
|
||||||
|
@ -40,7 +40,7 @@ fun UserProfileView(chatModel: ChatModel, close: () -> Unit) {
|
|||||||
profile = profile,
|
profile = profile,
|
||||||
close,
|
close,
|
||||||
saveProfile = { displayName, fullName, image ->
|
saveProfile = { displayName, fullName, image ->
|
||||||
withApi {
|
withBGApi {
|
||||||
val updated = chatModel.controller.apiUpdateProfile(user.remoteHostId, profile.copy(displayName = displayName.trim(), fullName = fullName, image = image))
|
val updated = chatModel.controller.apiUpdateProfile(user.remoteHostId, profile.copy(displayName = displayName.trim(), fullName = fullName, image = image))
|
||||||
if (updated != null) {
|
if (updated != null) {
|
||||||
val (newProfile, _) = updated
|
val (newProfile, _) = updated
|
||||||
|
@ -139,6 +139,10 @@
|
|||||||
<string name="smp_server_test_delete_file">Delete file</string>
|
<string name="smp_server_test_delete_file">Delete file</string>
|
||||||
<string name="error_deleting_user">Error deleting user profile</string>
|
<string name="error_deleting_user">Error deleting user profile</string>
|
||||||
<string name="error_updating_user_privacy">Error updating user privacy</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 -->
|
<!-- background service notice - SimpleXAPI.kt -->
|
||||||
<string name="icon_descr_instant_notifications">Instant notifications</string>
|
<string name="icon_descr_instant_notifications">Instant notifications</string>
|
||||||
@ -686,7 +690,9 @@
|
|||||||
<string name="hide_dev_options">Hide:</string>
|
<string name="hide_dev_options">Hide:</string>
|
||||||
<string name="show_developer_options">Show developer options</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">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_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_question">Shutdown?</string>
|
||||||
<string name="shutdown_alert_desc">Notifications will stop working until you re-launch the app</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
|
// 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) }
|
var hiddenUntilRestart by remember { mutableStateOf(false) }
|
||||||
if (!hiddenUntilRestart) {
|
if (!hiddenUntilRestart) {
|
||||||
val cWindowState = rememberWindowState(placement = WindowPlacement.Floating, width = DEFAULT_START_MODAL_WIDTH, height = 768.dp)
|
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.platform.UriHandler
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.views.helpers.generalGetString
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.helpers.withApi
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
@ -23,7 +22,7 @@ actual fun ClipboardManager.shareText(text: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
actual fun shareFile(text: String, fileSource: CryptoFile) {
|
actual fun shareFile(text: String, fileSource: CryptoFile) {
|
||||||
withApi {
|
withBGApi {
|
||||||
FileChooserLauncher(false) { to: URI? ->
|
FileChooserLauncher(false) { to: URI? ->
|
||||||
if (to != null) {
|
if (to != null) {
|
||||||
val absolutePath = if (fileSource.isAbsolutePath) fileSource.filePath else getAppFilePath(fileSource.filePath)
|
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 = {
|
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 = {
|
val saveIfExists = {
|
||||||
when (cItem.content.msgContent) {
|
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 -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
showMenu.value = false
|
showMenu.value = false
|
||||||
|
@ -43,7 +43,7 @@ actual fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<An
|
|||||||
.combinedClickable(onClick = {
|
.combinedClickable(onClick = {
|
||||||
val chat = chatModel.getChat(call.contact.id)
|
val chat = chatModel.getChat(call.contact.id)
|
||||||
if (chat != null) {
|
if (chat != null) {
|
||||||
withApi {
|
withBGApi {
|
||||||
openChat(chat.remoteHostId, chat.chatInfo, chatModel)
|
openChat(chat.remoteHostId, chat.chatInfo, chatModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package chat.simplex.common.views.database
|
|||||||
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import chat.simplex.common.platform.chatModel
|
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.coroutines.delay
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ actual fun restartChatOrApp() {
|
|||||||
startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged)
|
startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged)
|
||||||
} else {
|
} else {
|
||||||
authStopChat(chatModel) {
|
authStopChat(chatModel) {
|
||||||
withApi {
|
withBGApi {
|
||||||
// adding delay in order to prevent locked database by previous initialization
|
// adding delay in order to prevent locked database by previous initialization
|
||||||
delay(1000)
|
delay(1000)
|
||||||
chatModel.chatDbChanged.value = true
|
chatModel.chatDbChanged.value = true
|
||||||
|
@ -45,7 +45,7 @@ actual fun GetImageBottomSheet(
|
|||||||
}
|
}
|
||||||
val pickImageLauncher = rememberFileChooserLauncher(true, null, processPickedImage)
|
val pickImageLauncher = rememberFileChooserLauncher(true, null, processPickedImage)
|
||||||
ActionButton(null, stringResource(MR.strings.from_gallery_button), icon = painterResource(MR.images.ic_image)) {
|
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") }
|
val state = rememberSaveable { mutableStateOf(languagePref.get() ?: "system") }
|
||||||
LangSelector(state) {
|
LangSelector(state) {
|
||||||
state.value = it
|
state.value = it
|
||||||
withApi {
|
withBGApi {
|
||||||
delay(200)
|
delay(200)
|
||||||
if (it == "system") {
|
if (it == "system") {
|
||||||
languagePref.set(null)
|
languagePref.set(null)
|
||||||
|
@ -3,6 +3,7 @@ package chat.simplex.common.views.usersettings
|
|||||||
import SectionView
|
import SectionView
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import chat.simplex.common.model.ChatModel
|
import chat.simplex.common.model.ChatModel
|
||||||
|
import chat.simplex.common.views.helpers.ModalData
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
@ -10,7 +11,7 @@ import dev.icerock.moko.resources.compose.stringResource
|
|||||||
@Composable
|
@Composable
|
||||||
actual fun SettingsSectionApp(
|
actual fun SettingsSectionApp(
|
||||||
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit),
|
||||||
showCustomModal: (@Composable (ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit),
|
||||||
showVersion: () -> Unit,
|
showVersion: () -> Unit,
|
||||||
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
|
withAuth: (title: String, desc: String, block: () -> Unit) -> Unit
|
||||||
) {
|
) {
|
||||||
|
Loading…
Reference in New Issue
Block a user