mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-05-25 12:04:22 +00:00
Merge branch 'master' into master-android
This commit is contained in:
@@ -5,10 +5,11 @@ plugins {
|
||||
id("org.jetbrains.compose")
|
||||
kotlin("android")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk = 34
|
||||
compileSdk = 35
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "chat.simplex.app"
|
||||
@@ -191,7 +192,7 @@ tasks {
|
||||
outputDir = outputs.files.files.last()
|
||||
}
|
||||
exec {
|
||||
workingDir("../../../scripts/android")
|
||||
workingDir("../../scripts/android")
|
||||
environment = mapOf("JAVA_HOME" to "$javaHome")
|
||||
commandLine = listOf(
|
||||
"./compress-and-sign-apk.sh",
|
||||
|
||||
@@ -112,7 +112,7 @@ class SimplexService: Service() {
|
||||
val title = generalGetString(MR.strings.simplex_service_notification_title)
|
||||
val text = generalGetString(MR.strings.simplex_service_notification_text)
|
||||
notificationManager = createNotificationChannel()
|
||||
val newNtf = createNotification(title, text)
|
||||
val newNtf = createServiceNotification(title, text)
|
||||
serviceNotification = newNtf
|
||||
return newNtf
|
||||
}
|
||||
@@ -167,8 +167,8 @@ class SimplexService: Service() {
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun createNotification(title: String, text: String): Notification {
|
||||
|
||||
private fun createServiceNotification(title: String, text: String): Notification {
|
||||
val pendingIntent: PendingIntent = Intent(this, MainActivity::class.java).let { notificationIntent ->
|
||||
PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
}
|
||||
@@ -181,6 +181,7 @@ class SimplexService: Service() {
|
||||
.setContentIntent(pendingIntent)
|
||||
.setSilent(true)
|
||||
.setShowWhen(false) // no date/time
|
||||
.setOngoing(true) // Starting SDK 33 / Android 13, foreground notifications can be swiped away
|
||||
|
||||
// Shows a button which opens notification channel settings
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
||||
@@ -3,6 +3,7 @@ plugins {
|
||||
id("org.jetbrains.compose")
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.plugin.serialization")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
id("dev.icerock.mobile.multiplatform-resources")
|
||||
id("com.github.gmazzo.buildconfig") version "5.3.5"
|
||||
}
|
||||
@@ -39,6 +40,8 @@ kotlin {
|
||||
api("com.russhwolf:multiplatform-settings:1.1.1")
|
||||
api("com.charleskorn.kaml:kaml:0.59.0")
|
||||
api("org.jetbrains.compose.ui:ui-text:${rootProject.extra["compose.version"] as String}")
|
||||
implementation("org.jetbrains.compose.material:material-icons-core:1.7.3")
|
||||
implementation("org.jetbrains.compose.material:material-icons-extended:1.7.3")
|
||||
implementation("org.jetbrains.compose.components:components-animatedimage:${rootProject.extra["compose.version"] as String}")
|
||||
//Barcode
|
||||
api("org.boofcv:boofcv-core:1.1.3")
|
||||
@@ -125,7 +128,7 @@ kotlin {
|
||||
|
||||
android {
|
||||
namespace = "chat.simplex.common"
|
||||
compileSdk = 34
|
||||
compileSdk = 35
|
||||
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
|
||||
defaultConfig {
|
||||
minSdk = 26
|
||||
|
||||
+4
-2
@@ -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;
|
||||
}
|
||||
|
||||
+13
-6
@@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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()
|
||||
|
||||
+1
-1
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+2
-1
@@ -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()) {
|
||||
|
||||
+2
-2
@@ -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))
|
||||
|
||||
+1
-1
@@ -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 {
|
||||
|
||||
+1
@@ -58,6 +58,7 @@ class ProcessedErrors <T: AgentErrorType>(val interval: Long) {
|
||||
text = generalGetString(MR.strings.agent_internal_error_desc).format(error.internalErr),
|
||||
)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -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 }
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
|
||||
+4
-2
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import org.gradle.internal.extensions.stdlib.toDefaultLowerCase
|
||||
import org.jetbrains.compose.desktop.application.dsl.TargetFormat
|
||||
import org.jetbrains.kotlin.util.capitalizeDecapitalize.toLowerCaseAsciiOnly
|
||||
|
||||
plugins {
|
||||
kotlin("multiplatform")
|
||||
id("org.jetbrains.compose")
|
||||
id("org.jetbrains.kotlin.plugin.compose")
|
||||
id("io.github.tomtzook.gradle-cmake") version "1.2.2"
|
||||
}
|
||||
|
||||
@@ -89,7 +90,7 @@ compose {
|
||||
}
|
||||
}
|
||||
}
|
||||
val os = System.getProperty("os.name", "generic").toLowerCaseAsciiOnly()
|
||||
val os = System.getProperty("os.name", "generic").toDefaultLowerCase()
|
||||
if (os.contains("mac") || os.contains("win")) {
|
||||
packageName = "SimpleX"
|
||||
} else {
|
||||
|
||||
@@ -32,9 +32,9 @@ android.bundle=false
|
||||
desktop.version_name=6.4.3.1
|
||||
desktop.version_code=117
|
||||
|
||||
kotlin.version=1.9.23
|
||||
gradle.plugin.version=8.2.0
|
||||
compose.version=1.7.0
|
||||
kotlin.version=2.1.20
|
||||
gradle.plugin.version=8.7.0
|
||||
compose.version=1.8.2
|
||||
|
||||
# Choose sqlite or postgres backend
|
||||
database.backend=sqlite
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#Mon Feb 14 14:23:51 GMT 2022
|
||||
#Fri Mar 21 20:38:56 ICT 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
@@ -12,6 +12,7 @@ pluginManagement {
|
||||
id("com.android.application").version(extra["gradle.plugin.version"] as String)
|
||||
id("com.android.library").version(extra["gradle.plugin.version"] as String)
|
||||
id("org.jetbrains.compose").version(extra["compose.version"] as String)
|
||||
id("org.jetbrains.kotlin.plugin.compose").version(extra["kotlin.version"] as String)
|
||||
id("org.jetbrains.kotlin.plugin.serialization").version(extra["kotlin.version"] as String)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user