Merge branch 'master' into master-ghc8107

This commit is contained in:
Evgeny Poberezkin
2023-12-21 00:45:10 +00:00
72 changed files with 1070 additions and 448 deletions
@@ -4,7 +4,7 @@ import android.app.Activity
import android.content.Context
import android.content.pm.ActivityInfo
import android.graphics.Rect
import android.os.Build
import android.os.*
import android.view.*
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
@@ -12,7 +12,6 @@ import androidx.activity.compose.setContent
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalView
import chat.simplex.common.AppScreen
import chat.simplex.common.ui.theme.SimpleXTheme
import chat.simplex.common.views.helpers.*
import androidx.compose.ui.platform.LocalContext as LocalContext1
import chat.simplex.res.MR
@@ -79,6 +78,7 @@ actual fun androidIsFinishingMainActivity(): Boolean = (mainActivity.get()?.isFi
actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler {
actual override fun uncaughtException(thread: Thread, e: Throwable) {
Log.e(TAG, "App crashed, thread name: " + thread.name + ", exception: " + e.stackTraceToString())
includeMoreFailedComposables()
if (ModalManager.start.hasModalsOpen()) {
ModalManager.start.closeModal()
} else if (chatModel.chatId.value != null) {
@@ -93,19 +93,25 @@ actual class GlobalExceptionsHandler: Thread.UncaughtExceptionHandler {
chatModel.callManager.endCall(it)
}
}
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.app_was_crashed),
text = e.stackTraceToString()
)
//mainActivity.get()?.recreate()
mainActivity.get()?.apply {
window
?.decorView
?.findViewById<ViewGroup>(android.R.id.content)
?.removeViewAt(0)
setContent {
AppScreen()
if (thread.name == "main") {
mainActivity.get()?.recreate()
} else {
mainActivity.get()?.apply {
window
?.decorView
?.findViewById<ViewGroup>(android.R.id.content)
?.removeViewAt(0)
setContent {
AppScreen()
}
}
}
// Wait until activity recreates to prevent showing two alerts (in case `main` was crashed)
Handler(Looper.getMainLooper()).post {
AlertManager.shared.showAlertMsg(
title = generalGetString(MR.strings.app_was_crashed),
text = e.stackTraceToString()
)
}
}
}
@@ -162,11 +162,26 @@ fun MainScreen() {
AuthView()
} else {
SplashView()
ModalManager.fullscreen.showPasscodeInView()
}
} else {
if (chatModel.showCallView.value) {
ActiveCallView()
} else {
// It's needed for privacy settings toggle, so it can be shown even if the app is passcode unlocked
ModalManager.fullscreen.showPasscodeInView()
}
AlertManager.privacySensitive.showInView()
if (onboarding == OnboardingStage.OnboardingComplete) {
LaunchedEffect(chatModel.currentUser.value, chatModel.appOpenUrl.value) {
val (rhId, url) = chatModel.appOpenUrl.value ?: (null to null)
if (url != null) {
chatModel.appOpenUrl.value = null
connectIfOpenedViaUri(rhId, url, chatModel)
}
}
}
} else if (chatModel.showCallView.value) {
ActiveCallView()
}
ModalManager.fullscreen.showPasscodeInView()
val invitation = chatModel.activeCallInvitation.value
if (invitation != null) IncomingCallAlertView(invitation, chatModel)
AlertManager.shared.showInView()
@@ -317,9 +332,11 @@ fun DesktopScreen(settingsState: SettingsViewState) {
)
}
VerticalDivider(Modifier.padding(start = DEFAULT_START_MODAL_WIDTH))
UserPicker(chatModel, userPickerState) {
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
userPickerState.value = AnimatedViewState.GONE
tryOrShowError("UserPicker", error = {}) {
UserPicker(chatModel, userPickerState) {
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
userPickerState.value = AnimatedViewState.GONE
}
}
ModalManager.fullscreen.showInView()
}
@@ -70,8 +70,8 @@ object ChatModel {
// Only needed during onboarding when user skipped password setup (left as random password)
val desktopOnboardingRandomPassword = mutableStateOf(false)
// set when app is opened via contact or invitation URI
val appOpenUrl = mutableStateOf<URI?>(null)
// set when app is opened via contact or invitation URI (rhId, uri)
val appOpenUrl = mutableStateOf<Pair<Long?, URI>?>(null)
// preferences
val notificationPreviewMode by lazy {
@@ -2023,7 +2023,8 @@ object ChatController {
chatModel.chatId.value = null
ModalManager.center.closeModals()
ModalManager.end.closeModals()
AlertManager.shared.alertViews.clear()
AlertManager.shared.hideAllAlerts()
AlertManager.privacySensitive.hideAllAlerts()
chatModel.currentRemoteHost.value = switchRemoteHost(rhId)
reloadRemoteHosts()
val user = apiGetActiveUser(rhId)
@@ -900,7 +900,11 @@ fun BoxWithConstraintsScope.ChatItemsList(
@Composable
fun ChatItemViewShortHand(cItem: ChatItem, range: IntRange?) {
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools)
tryOrShowError("${cItem.id}ChatItem", error = {
CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart)
}) {
ChatItemView(chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools)
}
}
@Composable
@@ -201,7 +201,7 @@ suspend fun MutableState<ComposeState>.processPickedMedia(uris: List<URI>, text:
// Image
val drawable = getDrawableFromUri(uri)
// Do not show alert in case it's already shown from the function above
bitmap = getBitmapFromUri(uri, withAlertOnException = AlertManager.shared.alertViews.isEmpty())
bitmap = getBitmapFromUri(uri, withAlertOnException = !AlertManager.shared.hasAlertsShown())
if (isAnimImage(uri, drawable)) {
// It's a gif or webp
val fileSize = getFileSize(uri)
@@ -0,0 +1,18 @@
package chat.simplex.common.views.chat.item
import androidx.compose.foundation.layout.*
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import chat.simplex.res.MR
@Composable
fun CIBrokenComposableView(alignment: Alignment) {
Box(Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 6.dp), contentAlignment = alignment) {
Text(stringResource(MR.strings.error_showing_message), color = MaterialTheme.colors.error, fontStyle = FontStyle.Italic)
}
}
@@ -14,6 +14,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -61,9 +62,17 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
is ChatInfo.Direct -> {
val contactNetworkStatus = chatModel.contactNetworkStatus(chat.chatInfo.contact)
ChatListNavLinkLayout(
chatLinkPreview = { ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, contactNetworkStatus, stopped, linkMode, inProgress = false, progressByTimeout = false) },
chatLinkPreview = {
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, contactNetworkStatus, stopped, linkMode, inProgress = false, progressByTimeout = false)
}
},
click = { directChatAction(chat.remoteHostId, chat.chatInfo.contact, chatModel) },
dropdownMenuItems = { ContactMenuItems(chat, chat.chatInfo.contact, chatModel, showMenu, showMarkRead) },
dropdownMenuItems = {
tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) {
ContactMenuItems(chat, chat.chatInfo.contact, chatModel, showMenu, showMarkRead)
}
},
showMenu,
stopped,
selectedChat
@@ -71,25 +80,45 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
}
is ChatInfo.Group ->
ChatListNavLinkLayout(
chatLinkPreview = { ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, stopped, linkMode, inProgress.value, progressByTimeout) },
chatLinkPreview = {
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
ChatPreviewView(chat, showChatPreviews, chatModel.draft.value, chatModel.draftChatId.value, chatModel.currentUser.value?.profile?.displayName, null, stopped, linkMode, inProgress.value, progressByTimeout)
}
},
click = { if (!inProgress.value) groupChatAction(chat.remoteHostId, chat.chatInfo.groupInfo, chatModel, inProgress) },
dropdownMenuItems = { GroupMenuItems(chat, chat.chatInfo.groupInfo, chatModel, showMenu, inProgress, showMarkRead) },
dropdownMenuItems = {
tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) {
GroupMenuItems(chat, chat.chatInfo.groupInfo, chatModel, showMenu, inProgress, showMarkRead)
}
},
showMenu,
stopped,
selectedChat
)
is ChatInfo.ContactRequest ->
ChatListNavLinkLayout(
chatLinkPreview = { ContactRequestView(chat.chatInfo) },
chatLinkPreview = {
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
ContactRequestView(chat.chatInfo)
}
},
click = { contactRequestAlertDialog(chat.remoteHostId, chat.chatInfo, chatModel) },
dropdownMenuItems = { ContactRequestMenuItems(chat.remoteHostId, chat.chatInfo, chatModel, showMenu) },
dropdownMenuItems = {
tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) {
ContactRequestMenuItems(chat.remoteHostId, chat.chatInfo, chatModel, showMenu)
}
},
showMenu,
stopped,
selectedChat
)
is ChatInfo.ContactConnection ->
ChatListNavLinkLayout(
chatLinkPreview = { ContactConnectionView(chat.chatInfo.contactConnection) },
chatLinkPreview = {
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
ContactConnectionView(chat.chatInfo.contactConnection)
}
},
click = {
ModalManager.center.closeModals()
ModalManager.end.closeModals()
@@ -97,7 +126,11 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
ContactConnectionInfoView(chatModel, chat.remoteHostId, chat.chatInfo.contactConnection.connReqInv, chat.chatInfo.contactConnection, false, close)
}
},
dropdownMenuItems = { ContactConnectionMenuItems(chat.remoteHostId, chat.chatInfo, chatModel, showMenu) },
dropdownMenuItems = {
tryOrShowError("${chat.id}ChatListNavLinkDropdown", error = {}) {
ContactConnectionMenuItems(chat.remoteHostId, chat.chatInfo, chatModel, showMenu)
}
},
showMenu,
stopped,
selectedChat
@@ -105,7 +138,9 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
is ChatInfo.InvalidJSON ->
ChatListNavLinkLayout(
chatLinkPreview = {
InvalidDataView()
tryOrShowError("${chat.id}ChatListNavLink", error = { ErrorChatListItem() }) {
InvalidDataView()
}
},
click = {
ModalManager.end.closeModals()
@@ -119,6 +154,13 @@ fun ChatListNavLinkView(chat: Chat, chatModel: ChatModel) {
}
}
@Composable
private fun ErrorChatListItem() {
Box(Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 6.dp)) {
Text(stringResource(MR.strings.error_showing_content), color = MaterialTheme.colors.error, fontStyle = FontStyle.Italic)
}
}
fun directChatAction(rhId: Long?, contact: Contact, chatModel: ChatModel) {
when {
contact.activeConn == null && contact.profile.contactLink != null -> askCurrentOrIncognitoProfileConnectContactViaAddress(chatModel, rhId, contact, close = null, openChat = true)
@@ -611,12 +653,12 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress(
close: (() -> Unit)?,
openChat: Boolean
) {
AlertManager.shared.showAlertDialogButtonsColumn(
AlertManager.privacySensitive.showAlertDialogButtonsColumn(
title = String.format(generalGetString(MR.strings.connect_with_contact_name_question), contact.chatViewName),
buttons = {
Column {
SectionItemView({
AlertManager.shared.hideAlert()
AlertManager.privacySensitive.hideAlert()
withApi {
close?.invoke()
val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = false)
@@ -628,7 +670,7 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress(
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
SectionItemView({
AlertManager.shared.hideAlert()
AlertManager.privacySensitive.hideAlert()
withApi {
close?.invoke()
val ok = connectContactViaAddress(chatModel, rhId, contact.contactId, incognito = true)
@@ -640,7 +682,7 @@ fun askCurrentOrIncognitoProfileConnectContactViaAddress(
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
SectionItemView({
AlertManager.shared.hideAlert()
AlertManager.privacySensitive.hideAlert()
}) {
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
@@ -654,7 +696,7 @@ suspend fun connectContactViaAddress(chatModel: ChatModel, rhId: Long?, contactI
val contact = chatModel.controller.apiConnectContactViaAddress(rhId, incognito, contactId)
if (contact != null) {
chatModel.updateContact(rhId, contact)
AlertManager.shared.showAlertMsg(
AlertManager.privacySensitive.showAlertMsg(
title = generalGetString(MR.strings.connection_request_sent),
text = generalGetString(MR.strings.you_will_be_connected_when_your_connection_request_is_accepted),
hostDevice = hostDevice(rhId),
@@ -11,6 +11,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.*
import androidx.compose.ui.text.font.FontStyle
import dev.icerock.moko.resources.compose.painterResource
import dev.icerock.moko.resources.compose.stringResource
import androidx.compose.ui.text.font.FontWeight
@@ -49,13 +50,6 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
LaunchedEffect(chatModel.clearOverlays.value) {
if (chatModel.clearOverlays.value && newChatSheetState.value.isVisible()) hideNewChatSheet(false)
}
LaunchedEffect(chatModel.appOpenUrl.value) {
val url = chatModel.appOpenUrl.value
if (url != null) {
chatModel.appOpenUrl.value = null
connectIfOpenedViaUri(chatModel.remoteHostId(), url, chatModel)
}
}
if (appPlatform.isDesktop) {
KeyChangeEffect(chatModel.chatId.value) {
if (chatModel.chatId.value != null) {
@@ -71,7 +65,11 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
val (userPickerState, scaffoldState ) = settingsState
Scaffold(topBar = { Box(Modifier.padding(end = endPadding)) { ChatListToolbar(chatModel, scaffoldState.drawerState, userPickerState, stopped) { searchInList = it.trim() } } },
scaffoldState = scaffoldState,
drawerContent = { SettingsView(chatModel, setPerformLA, scaffoldState.drawerState) },
drawerContent = {
tryOrShowError("Settings", error = { ErrorSettingsView() }) {
SettingsView(chatModel, setPerformLA, scaffoldState.drawerState)
}
},
drawerScrimColor = MaterialTheme.colors.onSurface.copy(alpha = if (isInDarkTheme()) 0.16f else 0.32f),
drawerGesturesEnabled = appPlatform.isAndroid,
floatingActionButton = {
@@ -118,12 +116,16 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf
if (searchInList.isEmpty()) {
DesktopActiveCallOverlayLayout(newChatSheetState)
// TODO disable this button and sheet for the duration of the switch
NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet)
tryOrShowError("NewChatSheet", error = {}) {
NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet)
}
}
if (appPlatform.isAndroid) {
UserPicker(chatModel, userPickerState) {
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
userPickerState.value = AnimatedViewState.GONE
tryOrShowError("UserPicker", error = {}) {
UserPicker(chatModel, userPickerState) {
scope.launch { if (scaffoldState.drawerState.isOpen) scaffoldState.drawerState.close() else scaffoldState.drawerState.open() }
userPickerState.value = AnimatedViewState.GONE
}
}
}
}
@@ -302,7 +304,7 @@ expect fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<An
fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) {
Log.d(TAG, "connectIfOpenedViaUri: opened via link")
if (chatModel.currentUser.value == null) {
chatModel.appOpenUrl.value = uri
chatModel.appOpenUrl.value = rhId to uri
} else {
withApi {
planAndConnect(chatModel, rhId, uri, incognito = null, close = null)
@@ -310,6 +312,13 @@ fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) {
}
}
@Composable
private fun ErrorSettingsView() {
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
Text(generalGetString(MR.strings.error_showing_content), color = MaterialTheme.colors.error, fontStyle = FontStyle.Italic)
}
}
private var lazyListState = 0 to 0
@Composable
@@ -47,10 +47,12 @@ fun ShareListView(chatModel: ChatModel, settingsState: SettingsViewState, stoppe
}
}
if (appPlatform.isAndroid) {
UserPicker(chatModel, userPickerState, showSettings = false, showCancel = true, cancelClicked = {
chatModel.sharedContent.value = null
userPickerState.value = AnimatedViewState.GONE
})
tryOrShowError("UserPicker", error = {}) {
UserPicker(chatModel, userPickerState, showSettings = false, showCancel = true, cancelClicked = {
chatModel.sharedContent.value = null
userPickerState.value = AnimatedViewState.GONE
})
}
}
}
@@ -24,7 +24,7 @@ import dev.icerock.moko.resources.StringResource
import dev.icerock.moko.resources.compose.painterResource
class AlertManager {
var alertViews = mutableStateListOf<(@Composable () -> Unit)>()
private var alertViews = mutableStateListOf<(@Composable () -> Unit)>()
fun showAlert(alert: @Composable () -> Unit) {
Log.d(TAG, "AlertManager.showAlert")
@@ -35,6 +35,12 @@ class AlertManager {
alertViews.removeLastOrNull()
}
fun hideAllAlerts() {
alertViews.clear()
}
fun hasAlertsShown() = alertViews.isNotEmpty()
fun showAlertDialogButtons(
title: String,
text: String? = null,
@@ -220,6 +226,7 @@ class AlertManager {
companion object {
val shared = AlertManager()
val privacySensitive = AlertManager()
}
}
@@ -390,6 +390,28 @@ fun IntSize.Companion.Saver(): Saver<IntSize, *> = Saver(
restore = { IntSize(it.first, it.second) }
)
private var lastExecutedComposables = HashSet<Any>()
private val failedComposables = HashSet<Any>()
@Composable
fun tryOrShowError(key: Any = Exception().stackTraceToString().lines()[2], error: @Composable () -> Unit = {}, content: @Composable () -> Unit) {
if (!failedComposables.contains(key)) {
lastExecutedComposables.add(key)
content()
lastExecutedComposables.remove(key)
} else {
error()
}
}
fun includeMoreFailedComposables() {
lastExecutedComposables.forEach {
failedComposables.add(it)
Log.i(TAG, "Added composable key as failed: $it")
}
lastExecutedComposables.clear()
}
@Composable
fun DisposableEffectOnGone(always: () -> Unit = {}, whenDispose: () -> Unit = {}, whenGone: () -> Unit) {
DisposableEffect(Unit) {
@@ -70,7 +70,8 @@ private fun deleteStorageAndRestart(m: ChatModel, password: String, completed: (
m.controller.startChat(createdUser)
}
ModalManager.fullscreen.closeModals()
AlertManager.shared.hideAlert()
AlertManager.shared.hideAllAlerts()
AlertManager.privacySensitive.hideAllAlerts()
completed(LAResult.Success)
} catch (e: Exception) {
completed(LAResult.Error(generalGetString(MR.strings.incorrect_passcode)))
@@ -67,7 +67,7 @@ fun QRCode(
scope.launch {
val image = qrCodeBitmap(connReq, 1024).replaceColor(Color.Black.toArgb(), tintColor.toArgb())
.let { if (withLogo) it.addLogo() else it }
val file = saveTempImageUncompressed(image, false)
val file = saveTempImageUncompressed(image, true)
if (file != null) {
shareFile("", CryptoFile.plain(file.absolutePath))
}
@@ -20,7 +20,7 @@ import chat.simplex.common.platform.*
import chat.simplex.common.ui.theme.*
import chat.simplex.common.views.chatlist.*
import chat.simplex.common.views.helpers.*
import chat.simplex.common.views.usersettings.*
import chat.simplex.common.views.usersettings.IncognitoView
import chat.simplex.res.MR
import java.net.URI
@@ -58,7 +58,7 @@ suspend fun planAndConnect(
InvitationLinkPlan.OwnLink -> {
Log.d(TAG, "planAndConnect, .InvitationLink, .OwnLink, incognito=$incognito")
if (incognito != null) {
AlertManager.shared.showAlertDialog(
AlertManager.privacySensitive.showAlertDialog(
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
text = generalGetString(MR.strings.connect_plan_this_is_your_own_one_time_link),
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
@@ -80,13 +80,13 @@ suspend fun planAndConnect(
val contact = connectionPlan.invitationLinkPlan.contact_
if (contact != null) {
openKnownContact(chatModel, rhId, close, contact)
AlertManager.shared.showAlertMsg(
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName),
hostDevice = hostDevice(rhId),
)
} else {
AlertManager.shared.showAlertMsg(
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.connect_plan_already_connecting),
generalGetString(MR.strings.connect_plan_you_are_already_connecting_via_this_one_time_link),
hostDevice = hostDevice(rhId),
@@ -97,7 +97,7 @@ suspend fun planAndConnect(
Log.d(TAG, "planAndConnect, .InvitationLink, .Known, incognito=$incognito")
val contact = connectionPlan.invitationLinkPlan.contact
openKnownContact(chatModel, rhId, close, contact)
AlertManager.shared.showAlertMsg(
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName),
hostDevice = hostDevice(rhId),
@@ -121,7 +121,7 @@ suspend fun planAndConnect(
ContactAddressPlan.OwnLink -> {
Log.d(TAG, "planAndConnect, .ContactAddress, .OwnLink, incognito=$incognito")
if (incognito != null) {
AlertManager.shared.showAlertDialog(
AlertManager.privacySensitive.showAlertDialog(
title = generalGetString(MR.strings.connect_plan_connect_to_yourself),
text = generalGetString(MR.strings.connect_plan_this_is_your_own_simplex_address),
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
@@ -141,7 +141,7 @@ suspend fun planAndConnect(
ContactAddressPlan.ConnectingConfirmReconnect -> {
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingConfirmReconnect, incognito=$incognito")
if (incognito != null) {
AlertManager.shared.showAlertDialog(
AlertManager.privacySensitive.showAlertDialog(
title = generalGetString(MR.strings.connect_plan_repeat_connection_request),
text = generalGetString(MR.strings.connect_plan_you_have_already_requested_connection_via_this_address),
confirmText = if (incognito) generalGetString(MR.strings.connect_via_link_incognito) else generalGetString(MR.strings.connect_via_link_verb),
@@ -162,7 +162,7 @@ suspend fun planAndConnect(
Log.d(TAG, "planAndConnect, .ContactAddress, .ConnectingProhibit, incognito=$incognito")
val contact = connectionPlan.contactAddressPlan.contact
openKnownContact(chatModel, rhId, close, contact)
AlertManager.shared.showAlertMsg(
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_connecting_to_vName), contact.displayName),
hostDevice = hostDevice(rhId),
@@ -172,7 +172,7 @@ suspend fun planAndConnect(
Log.d(TAG, "planAndConnect, .ContactAddress, .Known, incognito=$incognito")
val contact = connectionPlan.contactAddressPlan.contact
openKnownContact(chatModel, rhId, close, contact)
AlertManager.shared.showAlertMsg(
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.contact_already_exists),
String.format(generalGetString(MR.strings.you_are_already_connected_to_vName_via_this_link), contact.displayName),
hostDevice = hostDevice(rhId),
@@ -193,7 +193,7 @@ suspend fun planAndConnect(
GroupLinkPlan.Ok -> {
Log.d(TAG, "planAndConnect, .GroupLink, .Ok, incognito=$incognito")
if (incognito != null) {
AlertManager.shared.showAlertDialog(
AlertManager.privacySensitive.showAlertDialog(
title = generalGetString(MR.strings.connect_via_group_link),
text = generalGetString(MR.strings.you_will_join_group),
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
@@ -217,7 +217,7 @@ suspend fun planAndConnect(
GroupLinkPlan.ConnectingConfirmReconnect -> {
Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingConfirmReconnect, incognito=$incognito")
if (incognito != null) {
AlertManager.shared.showAlertDialog(
AlertManager.privacySensitive.showAlertDialog(
title = generalGetString(MR.strings.connect_plan_repeat_join_request),
text = generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link),
confirmText = if (incognito) generalGetString(MR.strings.join_group_incognito_button) else generalGetString(MR.strings.join_group_button),
@@ -238,12 +238,12 @@ suspend fun planAndConnect(
Log.d(TAG, "planAndConnect, .GroupLink, .ConnectingProhibit, incognito=$incognito")
val groupInfo = connectionPlan.groupLinkPlan.groupInfo_
if (groupInfo != null) {
AlertManager.shared.showAlertMsg(
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.connect_plan_group_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_vName), groupInfo.displayName)
)
} else {
AlertManager.shared.showAlertMsg(
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.connect_plan_already_joining_the_group),
generalGetString(MR.strings.connect_plan_you_are_already_joining_the_group_via_this_link),
hostDevice = hostDevice(rhId),
@@ -254,7 +254,7 @@ suspend fun planAndConnect(
Log.d(TAG, "planAndConnect, .GroupLink, .Known, incognito=$incognito")
val groupInfo = connectionPlan.groupLinkPlan.groupInfo
openKnownGroup(chatModel, rhId, close, groupInfo)
AlertManager.shared.showAlertMsg(
AlertManager.privacySensitive.showAlertMsg(
generalGetString(MR.strings.connect_plan_group_already_exists),
String.format(generalGetString(MR.strings.connect_plan_you_are_already_in_group_vName), groupInfo.displayName),
hostDevice = hostDevice(rhId),
@@ -289,7 +289,7 @@ suspend fun connectViaUri(
if (pcc != null) {
chatModel.updateContactConnection(rhId, pcc)
close?.invoke()
AlertManager.shared.showAlertMsg(
AlertManager.privacySensitive.showAlertMsg(
title = generalGetString(MR.strings.connection_request_sent),
text =
when (connLinkType) {
@@ -320,14 +320,14 @@ fun askCurrentOrIncognitoProfileAlert(
text: AnnotatedString? = null,
connectDestructive: Boolean,
) {
AlertManager.shared.showAlertDialogButtonsColumn(
AlertManager.privacySensitive.showAlertDialogButtonsColumn(
title = title,
text = text,
buttons = {
Column {
val connectColor = if (connectDestructive) MaterialTheme.colors.error else MaterialTheme.colors.primary
SectionItemView({
AlertManager.shared.hideAlert()
AlertManager.privacySensitive.hideAlert()
withApi {
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close)
}
@@ -335,7 +335,7 @@ fun askCurrentOrIncognitoProfileAlert(
Text(generalGetString(MR.strings.connect_use_current_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
}
SectionItemView({
AlertManager.shared.hideAlert()
AlertManager.privacySensitive.hideAlert()
withApi {
connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close)
}
@@ -343,7 +343,7 @@ fun askCurrentOrIncognitoProfileAlert(
Text(generalGetString(MR.strings.connect_use_new_incognito_profile), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = connectColor)
}
SectionItemView({
AlertManager.shared.hideAlert()
AlertManager.privacySensitive.hideAlert()
}) {
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
@@ -372,14 +372,14 @@ fun ownGroupLinkConfirmConnect(
groupInfo: GroupInfo,
close: (() -> Unit)?,
) {
AlertManager.shared.showAlertDialogButtonsColumn(
AlertManager.privacySensitive.showAlertDialogButtonsColumn(
title = generalGetString(MR.strings.connect_plan_join_your_group),
text = AnnotatedString(String.format(generalGetString(MR.strings.connect_plan_this_is_your_link_for_group_vName), groupInfo.displayName)),
buttons = {
Column {
// Open group
SectionItemView({
AlertManager.shared.hideAlert()
AlertManager.privacySensitive.hideAlert()
openKnownGroup(chatModel, rhId, close, groupInfo)
}) {
Text(generalGetString(MR.strings.connect_plan_open_group), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
@@ -387,7 +387,7 @@ fun ownGroupLinkConfirmConnect(
if (incognito != null) {
// Join incognito / Join with current profile
SectionItemView({
AlertManager.shared.hideAlert()
AlertManager.privacySensitive.hideAlert()
withApi {
connectViaUri(chatModel, rhId, uri, incognito, connectionPlan, close)
}
@@ -400,7 +400,7 @@ fun ownGroupLinkConfirmConnect(
} else {
// Use current profile
SectionItemView({
AlertManager.shared.hideAlert()
AlertManager.privacySensitive.hideAlert()
withApi {
connectViaUri(chatModel, rhId, uri, incognito = false, connectionPlan, close)
}
@@ -409,7 +409,7 @@ fun ownGroupLinkConfirmConnect(
}
// Use new incognito profile
SectionItemView({
AlertManager.shared.hideAlert()
AlertManager.privacySensitive.hideAlert()
withApi {
connectViaUri(chatModel, rhId, uri, incognito = true, connectionPlan, close)
}
@@ -419,7 +419,7 @@ fun ownGroupLinkConfirmConnect(
}
// Cancel
SectionItemView({
AlertManager.shared.hideAlert()
AlertManager.privacySensitive.hideAlert()
}) {
Text(stringResource(MR.strings.cancel_verb), Modifier.fillMaxWidth(), textAlign = TextAlign.Center, color = MaterialTheme.colors.primary)
}
@@ -150,7 +150,6 @@
<string name="add_new_contact_to_create_one_time_QR_code"><![CDATA[<b> إضافة جهة اتصال جديدة </b>: لإنشاء رمز الاستجابة السريعة الخاص بك لمرة واحدة لجهة اتصالك.]]></string>
<string name="scan_QR_code_to_connect_to_contact_who_shows_QR_code"><![CDATA[<b> امسح رمز الاستجابة السريعة </b>: للاتصال بجهة الاتصال التي تعرض لك رمز الاستجابة السريعة.]]></string>
<string name="callstatus_in_progress">مكالمتك تحت الإجراء</string>
<string name="callstatus_ended">انتهت المكالمة</string>
<string name="change_database_passphrase_question">تغيير عبارة مرور قاعدة البيانات؟</string>
<string name="cannot_access_keychain">لا يمكن الوصول إلى Keystore لحفظ كلمة مرور قاعدة البيانات</string>
<string name="icon_descr_cancel_file_preview">إلغاء معاينة الملف</string>
@@ -45,6 +45,9 @@
<string name="moderated_description">moderated</string>
<string name="invalid_chat">invalid chat</string>
<string name="invalid_data">invalid data</string>
<string name="error_showing_message">error showing message</string>
<string name="error_showing_content">error showing content</string>
<string name="decryption_error">Decryption error</string>
<string name="encryption_renegotiation_error">Encryption re-negotiation error</string>
@@ -41,7 +41,7 @@
<string name="accept">Αποδοχή</string>
<string name="accept_connection_request__question">Αποδοχή αιτήματος σύνδεσης;</string>
<string name="callstatus_accepted">αποδεκτή κλήση</string>
<string name="network_enable_socks_info">Πρόσβαση στους διακομιστές μέσω SOCKS proxy στην πόρτα 9050; Ο διακομιστής μεσολάβησης (proxy server) πρέπει να είναι ενεργός πριν ενεργοποιηθεί αυτή η ρύθμιση.</string>
<string name="network_enable_socks_info">Πρόσβαση στους διακομιστές μέσω SOCKS proxy στην πόρτα %d; Ο διακομιστής μεσολάβησης (proxy server) πρέπει να είναι ενεργός πριν ενεργοποιηθεί αυτή η ρύθμιση.</string>
<string name="smp_servers_add">Προσθήκη διακομιστή…</string>
<string name="network_settings">Προχωρημένες ρυθμίσεις δικτύου</string>
<string name="v4_3_improved_server_configuration_desc">Προσθήκη διακομιστών μέσω σάρωσης QR κωδικών.</string>
@@ -298,7 +298,7 @@
<string name="icon_descr_cancel_live_message">Cancelar mensaje en directo</string>
<string name="confirm_verb">Confirmar</string>
<string name="clear_chat_menu_action">Vaciar</string>
<string name="app_version_code">Build de la aplicación</string>
<string name="app_version_code">Build de la aplicación: %s</string>
<string name="call_already_ended">¡La llamada ha terminado!</string>
<string name="rcv_conn_event_switch_queue_phase_completed">el servidor de envío ha cambiado para tí</string>
<string name="icon_descr_cancel_link_preview">cancelar vista previa del enlace</string>
@@ -171,7 +171,6 @@
<string name="chat_archive_header">Arkisto</string>
<string name="delete_chat_archive_question">Poista keskusteluarkisto\?</string>
<string name="archive_created_on_ts">Luotu %1$s</string>
<string name="rcv_group_event_changed_your_role">%s:n rooli muutettu %s:ksi</string>
<string name="rcv_group_event_group_deleted">poistettu ryhmä</string>
<string name="group_member_status_connecting">yhdistää</string>
<string name="group_member_status_accepted">yhdistäminen (hyväksytty)</string>
@@ -687,7 +687,6 @@
<string name="sender_cancelled_file_transfer">ファイル送信が中止されました。</string>
<string name="sender_may_have_deleted_the_connection_request">送信元が繋がりリクエストを削除したかもしれません。</string>
<string name="error_smp_test_server_auth">このサーバで待ち行列を作るには認証が必要です。パスワードをご確認ください。</string>
<string name="periodic_notifications_desc">アプリが定期的に新しいメッセージを受信します。一日の電池使用量が約3%で、プッシュ通知に頼らずに、あなたの端末のデータをサーバに送ることはありません。</string>
<string name="la_notice_title_simplex_lock">SimpleXロック</string>
<string name="enter_passphrase_notification_desc">通知を受けるには、データベースの暗証フレーズを入力してください。</string>
<string name="simplex_service_notification_title">SimpleX Chat サービス</string>
@@ -904,7 +903,6 @@
<string name="settings_section_title_support">SIMPLEX CHATを支援</string>
<string name="smp_servers_test_servers">テストサーバ</string>
<string name="switch_receiving_address_desc">受信アドレスは別のサーバーに変更されます。アドレス変更は送信者がオンラインになった後に完了します。</string>
<string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery"><![CDATA[あなたのプライバシーを守るために、このアプリはプッシュ通知の変わりに <b>SimpleX バックグラウンド・サービス</b> を使ってます。一日の電池使用量は約3%です。]]></string>
<string name="to_protect_privacy_simplex_has_ids_for_queues">あなたのプライバシーを守るために、他のアプリと違って、ユーザーIDの変わりに SimpleX メッセージ束毎にIDを配布し、各連絡先が別々と扱います。</string>
<string name="group_main_profile_sent">あなたのチャットプロフィールが他のグループメンバーに送られます。</string>
<string name="to_verify_compare">エンドツーエンド暗号化を確認するには、ご自分の端末と連絡先の端末のコードを比べます (スキャンします)。</string>
@@ -337,7 +337,7 @@
<string name="error_sending_message">Mesaj gönderilirken hata oluştu</string>
<string name="error_creating_address">Adres oluştururken hata oluştu</string>
<string name="error_changing_address">Adres değiştirirken hata oluştu</string>
<string name="contact_wants_to_connect_via_call">1$s sizinle şu yolla bağlantı kurmak istiyor</string>
<string name="contact_wants_to_connect_via_call">%1$s sizinle şu yolla bağlantı kurmak istiyor</string>
<string name="error_changing_message_deletion">Ayarları değiştirirken hata oluştu</string>
<string name="error_creating_link_for_group">Toplu konuşma bağlantısı oluştururken hata oluştu</string>
<string name="error_changing_role">Yetki değiştirirken hata oluştu</string>
@@ -747,9 +747,9 @@
<string name="impossible_to_recover_passphrase"><![CDATA[<b>Aklınızda bulunsun</b>: kaybederseniz, parolayı kurtaramaz veya değiştiremezsiniz.]]></string>
<string name="chat_archive_header">Sohbet arşivi</string>
<string name="chat_archive_section">SOHBET ARŞİVİ</string>
<string name="group_invitation_item_description">1$s grubuna davet</string>
<string name="group_invitation_item_description">%1$s grubuna davet</string>
<string name="join_group_question">Gruba katıl\?</string>
<string name="rcv_group_event_member_added">1$s davet edildi</string>
<string name="rcv_group_event_member_added">%1$s davet edildi</string>
<string name="rcv_group_event_invited_via_your_group_link">grup bağlantınız üzerinden davet edildi</string>
<string name="group_member_status_invited">davet edildi</string>
<string name="invite_to_group_button">Gruba davet edin</string>
@@ -1344,7 +1344,7 @@
<string name="v5_2_message_delivery_receipts_descr">我们错过的第二个\"√\"!✅</string>
<string name="setup_database_passphrase">设定数据库密码</string>
<string name="receipts_groups_title_disable">为群组禁用回执吗?</string>
<string name="rcv_group_event_3_members_connected">%s、%s 和 %d 已连接</string>
<string name="rcv_group_event_3_members_connected">%s、%s 和 %s 已连接</string>
<string name="fix_connection_not_supported_by_group_member">修复群组成员不支持的问题</string>
<string name="receipts_groups_override_enabled">已为 %d 组启用送达回执功能</string>
<string name="sync_connection_force_confirm">重新协商</string>
@@ -1427,7 +1427,6 @@
<string name="connect_plan_connect_via_link">通过链接进行连接吗?</string>
<string name="connect_plan_already_joining_the_group">已经加入了该群组!</string>
<string name="group_members_n">%s、 %s 和 %d 名成员</string>
<string name="moderated_items_description">%s 审核了 %d 条消息</string>
<string name="unblock_member_button">解封成员</string>
<string name="connect_plan_connect_to_yourself">连接到你自己?</string>
<string name="contact_tap_to_connect">轻按连接</string>
@@ -11,7 +11,7 @@
<string name="about_simplex_chat">關於 SimpleX Chat</string>
<string name="accept_connection_request__question">接受連接請求?</string>
<string name="callstatus_accepted">已接受通話</string>
<string name="network_enable_socks_info">要在端口啟用 SOCKS 代理伺服器嗎?在啟用這個選項之前,必須先啟用代理伺服器。</string>
<string name="network_enable_socks_info">要在端口啟用 SOCKS 代理伺服器嗎 %d?在啟用這個選項之前,必須先啟用代理伺服器。</string>
<string name="group_member_role_admin">管理員</string>
<string name="above_then_preposition_continuation">然後,選按:</string>
<string name="smp_servers_preset_add">新增預設伺服器</string>
@@ -45,6 +45,7 @@ fun showApp() {
Log.e(TAG, "App crashed, thread name: " + Thread.currentThread().name + ", exception: " + e.stackTraceToString())
window.dispatchEvent(WindowEvent(window, WindowEvent.WINDOW_CLOSING))
closedByError.value = true
includeMoreFailedComposables()
// If the left side of screen has open modal, it's probably caused the crash
if (ModalManager.start.hasModalsOpen()) {
ModalManager.start.closeModal()
@@ -5,11 +5,11 @@ import androidx.compose.ui.text.*
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.Density
import chat.simplex.common.model.*
import chat.simplex.common.model.CIFile
import chat.simplex.common.model.readCryptoFile
import chat.simplex.common.platform.*
import chat.simplex.common.simplexWindowState
import java.io.ByteArrayInputStream
import java.io.File
import java.io.*
import java.net.URI
import javax.imageio.ImageIO
import kotlin.io.encoding.Base64
@@ -148,9 +148,8 @@ actual suspend fun saveTempImageUncompressed(image: ImageBitmap, asPng: Boolean)
return if (file != null) {
try {
val ext = if (asPng) "png" else "jpg"
val newFile = File(file.absolutePath + File.separator + generateNewFileName("IMG", ext, File(getAppFilePath(""))))
// LALAL FILE IS EMPTY
ImageIO.write(image.toAwtImage(), ext.uppercase(), newFile.outputStream())
val newFile = File(file.absolutePath + File.separator + generateNewFileName("IMG", ext, File(file.absolutePath)))
ImageIO.write(image.toAwtImage(), ext, newFile.outputStream())
newFile
} catch (e: Exception) {
Log.e(TAG, "Util.kt saveTempImageUncompressed error: ${e.message}")