Merge branch 'master' into master-android

This commit is contained in:
Evgeny Poberezkin
2025-08-18 17:44:44 +01:00
63 changed files with 801 additions and 201 deletions
@@ -709,9 +709,11 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIM
.filterNotNull()
.collect {
while (callCommand.isNotEmpty()) {
val cmd = callCommand.removeFirst()
val cmd = callCommand.removeFirstOrNull()
Log.d(TAG, "WebRTCView LaunchedEffect executing $cmd")
processCommand(wv, cmd)
if (cmd != null) {
processCommand(wv, cmd)
}
}
}
}
@@ -64,7 +64,7 @@ extern char *chat_recv_msg(chat_ctrl ctrl); // deprecated
extern char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait);
extern char *chat_parse_markdown(const char *str);
extern char *chat_parse_server(const char *str);
extern char *chat_parse_uri(const char *str);
extern char *chat_parse_uri(const char *str, const int safe);
extern char *chat_password_hash(const char *pwd, const char *salt);
extern char *chat_valid_name(const char *name);
extern int chat_json_length(const char *str);
@@ -148,9 +148,9 @@ Java_chat_simplex_common_platform_CoreKt_chatParseServer(JNIEnv *env, __unused j
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatParseUri(JNIEnv *env, __unused jclass clazz, jstring str) {
Java_chat_simplex_common_platform_CoreKt_chatParseUri(JNIEnv *env, __unused jclass clazz, jstring str, jint safe) {
const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE);
jstring res = (*env)->NewStringUTF(env, chat_parse_uri(_str));
jstring res = (*env)->NewStringUTF(env, chat_parse_uri(_str, safe));
(*env)->ReleaseStringUTFChars(env, str, _str);
return res;
}
@@ -37,7 +37,7 @@ extern char *chat_recv_msg(chat_ctrl ctrl); // deprecated
extern char *chat_recv_msg_wait(chat_ctrl ctrl, const int wait);
extern char *chat_parse_markdown(const char *str);
extern char *chat_parse_server(const char *str);
extern char *chat_parse_uri(const char *str);
extern char *chat_parse_uri(const char *str, const int safe);
extern char *chat_password_hash(const char *pwd, const char *salt);
extern char *chat_valid_name(const char *name);
extern int chat_json_length(const char *str);
@@ -158,9 +158,9 @@ Java_chat_simplex_common_platform_CoreKt_chatParseServer(JNIEnv *env, jclass cla
}
JNIEXPORT jstring JNICALL
Java_chat_simplex_common_platform_CoreKt_chatParseUri(JNIEnv *env, jclass clazz, jstring str) {
Java_chat_simplex_common_platform_CoreKt_chatParseUri(JNIEnv *env, jclass clazz, jstring str, jint safe) {
const char *_str = encode_to_utf8_chars(env, str);
jstring res = decode_to_utf8_string(env, chat_parse_uri(_str));
jstring res = decode_to_utf8_string(env, chat_parse_uri(_str, safe));
(*env)->ReleaseStringUTFChars(env, str, _str);
return res;
}
@@ -26,6 +26,8 @@ import kotlinx.coroutines.flow.*
import kotlinx.coroutines.sync.Mutex
import kotlin.collections.removeAll as remAll
import kotlinx.datetime.*
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import kotlinx.serialization.*
import kotlinx.serialization.descriptors.*
@@ -472,14 +474,17 @@ object ChatModel {
}
fun removeLastChatItems() {
val removed: Triple<Long, Int, Boolean>
val remIndex: Int
val rem: ChatItem?
chatItems.value = SnapshotStateList<ChatItem>().apply {
addAll(chatItems.value)
val remIndex = lastIndex
val rem = removeLast()
removed = Triple(rem.id, remIndex, rem.isRcvNew)
remIndex = lastIndex
rem = removeLastOrNull()
}
if (rem != null) {
val removed = Triple(rem.id, remIndex, rem.isRcvNew)
chatState.itemsRemoved(listOf(removed), chatItems.value)
}
chatState.itemsRemoved(listOf(removed), chatItems.value)
}
suspend fun addChatItem(rhId: Long?, chatInfo: ChatInfo, cItem: ChatItem) {
@@ -4432,13 +4437,15 @@ enum class SimplexLinkType(val linkType: String) {
contact("contact"),
invitation("invitation"),
group("group"),
channel("channel");
channel("channel"),
relay("relay");
val description: String get() = generalGetString(when (this) {
contact -> MR.strings.simplex_link_contact
invitation -> MR.strings.simplex_link_invitation
group -> MR.strings.simplex_link_group
channel -> MR.strings.simplex_link_channel
relay -> MR.strings.simplex_link_relay
})
}
@@ -4634,8 +4634,8 @@ data class ParsedServerAddress (
var parseError: String
)
fun parseSanitizeUri(s: String): ParsedUri? {
val parsed = chatParseUri(s)
fun parseSanitizeUri(s: String, safe: Boolean): ParsedUri? {
val parsed = chatParseUri(s, if (safe) 1 else 0)
return runCatching { json.decodeFromString(ParsedUri.serializer(), parsed) }
.onFailure { Log.d(TAG, "parseSanitizeUri decode error: $it") }
.getOrNull()
@@ -32,7 +32,7 @@ val databaseBackend: String = if (appPlatform == AppPlatform.ANDROID) "sqlite" e
class FifoQueue<E>(private var capacity: Int) : LinkedList<E>() {
override fun add(element: E): Boolean {
if(size > capacity) removeFirst()
if (size > capacity) removeFirstOrNull()
return super.add(element)
}
}
@@ -28,7 +28,7 @@ external fun chatRecvMsg(ctrl: ChatCtrl): String
external fun chatRecvMsgWait(ctrl: ChatCtrl, timeout: Int): String
external fun chatParseMarkdown(str: String): String
external fun chatParseServer(str: String): String
external fun chatParseUri(str: String): String
external fun chatParseUri(str: String, safe: Int): String
external fun chatPasswordHash(pwd: String, salt: String): String
external fun chatValidName(name: String): String
external fun chatJsonLength(str: String): Int
@@ -325,7 +325,8 @@ private fun removeDuplicatesAndUpperSplits(
if (idsToTrim.last().isNotEmpty()) {
// it has some elements to trim from currently visible range which means the items shouldn't be trimmed
// Otherwise, the last set would be empty
idsToTrim.removeLast()
// note: removeLast() produce NoSuchMethodError on Android but removeLastOrNull() works
idsToTrim.removeLastOrNull()
}
val allItemsToDelete = idsToTrim.flatten()
if (allItemsToDelete.isNotEmpty()) {
@@ -876,7 +876,7 @@ fun ComposeView(
var updated = ft
when(ft.format) {
is Format.Uri -> {
val sanitized = parseSanitizeUri(ft.text)?.uriInfo?.sanitized
val sanitized = parseSanitizeUri(ft.text, safe = true)?.uriInfo?.sanitized
if (sanitized != null) {
updated = FormattedText(text = sanitized, format = Format.Uri())
pos += updated.text.count()
@@ -884,7 +884,7 @@ fun ComposeView(
}
}
is Format.HyperLink -> {
val sanitized = parseSanitizeUri(ft.format.linkUri)?.uriInfo?.sanitized
val sanitized = parseSanitizeUri(ft.format.linkUri, safe = true)?.uriInfo?.sanitized
if (sanitized != null) {
val updatedText = if (ft.format.showText == null) sanitized else "[${ft.format.showText}]($sanitized)"
updated = FormattedText(text = updatedText, format = Format.HyperLink(showText = ft.format.showText, linkUri = sanitized))
@@ -427,7 +427,7 @@ fun showInvalidLinkAlert(uri: String, error: String? = null) {
}
fun sanitizeUri(s: String): Pair<Pair<Boolean, String?>?, String?> {
val parsed = parseSanitizeUri(s)
val parsed = parseSanitizeUri(s, safe = false)
return if (parsed?.uriInfo != null) {
(true to parsed.uriInfo.sanitized) to null
} else {
@@ -58,6 +58,7 @@ class ProcessedErrors <T: AgentErrorType>(val interval: Long) {
text = generalGetString(MR.strings.agent_internal_error_desc).format(error.internalErr),
)
}
else -> {}
}
}
}
@@ -33,7 +33,7 @@ import chat.simplex.res.MR
import java.text.DecimalFormat
@Composable
fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> Unit, close: () -> Unit) {
fun ModalData.AdvancedNetworkSettingsView(showModal: (@Composable ModalData.() -> Unit) -> Unit, close: () -> Unit) {
val currentRemoteHost by remember { chatModel.currentRemoteHost }
val developerTools = remember { appPrefs.developerTools.get() }
@@ -216,7 +216,7 @@ fun ModalData.AdvancedNetworkSettingsView(showModal: (ModalData.() -> Unit) -> U
updateSessionMode: (TransportSessionMode) -> Unit,
updateSMPProxyMode: (SMPProxyMode) -> Unit,
updateSMPProxyFallback: (SMPProxyFallback) -> Unit,
showModal: (ModalData.() -> Unit) -> Unit,
showModal: (@Composable ModalData.() -> Unit) -> Unit,
resetDisabled: Boolean,
reset: () -> Unit,
saveDisabled: Boolean,
@@ -98,6 +98,7 @@
<string name="simplex_link_invitation">SimpleX one-time invitation</string>
<string name="simplex_link_group">SimpleX group link</string>
<string name="simplex_link_channel">SimpleX channel link</string>
<string name="simplex_link_relay">SimpleX relay link</string>
<string name="simplex_link_connection">via %1$s</string>
<string name="simplex_link_mode">SimpleX links</string>
<string name="simplex_link_mode_description">Description</string>
@@ -171,7 +171,7 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState<Boolean>) {
// Shows toast in insertion order with preferred delay per toast. New one will be shown once previous one expires
LaunchedEffect(toast, toasts.size) {
delay(toast.second)
simplexWindowState.toasts.removeFirst()
simplexWindowState.toasts.removeFirstOrNull()
}
}
var windowFocused by remember { simplexWindowState.windowFocused }
@@ -93,7 +93,7 @@ actual fun LazyColumnWithScrollBar(
}
val modifier = if (fillMaxSize) Modifier.fillMaxSize().then(modifier) else modifier
Box(Modifier.copyViewToAppBar(remember { appPrefs.appearanceBarsBlurRadius.state }.value, LocalAppBarHandler.current?.graphicsLayer).nestedScroll(connection)) {
LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content = content)
ScrollBar(reverseLayout, state, scrollBarAlpha, scrollJob, scrollBarDraggingState, additionalBarOffset, additionalTopBar, chatBottomBar)
}
}
@@ -138,7 +138,7 @@ actual fun LazyColumnWithScrollBarNoAppBar(
// (only first visible row is useful because LazyColumn doesn't have absolute scroll position, only relative to row)
val scrollBarDraggingState = remember { mutableStateOf(false) }
Box(contentAlignment = containerAlignment) {
LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content)
LazyColumn(modifier.then(scrollModifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content = content)
Box(if (maxHeight?.value != null) Modifier.height(maxHeight.value).fillMaxWidth() else Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) {
DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.fillMaxHeight(), scrollBarAlpha, scrollJob, reverseLayout, scrollBarDraggingState)
}
@@ -195,9 +195,11 @@ fun WebRTCController(callCommand: SnapshotStateList<WCallCommand>, onResponse: (
delay(100)
}
while (callCommand.isNotEmpty()) {
val cmd = callCommand.removeFirst()
val cmd = callCommand.removeFirstOrNull()
Log.d(TAG, "WebRTCController LaunchedEffect executing $cmd")
processCommand(cmd)
if (cmd != null) {
processCommand(cmd)
}
}
}
}