diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 91c41e562a..1d7eb6b3c7 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -95,6 +95,7 @@ kotlin { dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.7.1") implementation("com.github.Dansoftowner:jSystemThemeDetector:3.6") + implementation("org.slf4j:slf4j-simple:2.0.7") } } val desktopTest by getting diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt index 39b62a1c8b..192f3dcc29 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt @@ -25,7 +25,7 @@ val defaultLocale: Locale = Locale.getDefault() lateinit var androidAppContext: Context lateinit var mainActivity: WeakReference -actual fun initHaskell() { +fun initHaskell() { val socketName = "chat.simplex.app.local.socket.address.listen.native.cmd2" + Random.nextLong(100000) val s = Semaphore(0) thread(name="stdout/stderr pipe") { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt index e6c6ebad09..e9ff5687d9 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/PlatformTextField.android.kt @@ -47,7 +47,8 @@ actual fun PlatformTextField( textStyle: MutableState, showDeleteTextButton: MutableState, userIsObserver: Boolean, - onMessageChange: (String) -> Unit + onMessageChange: (String) -> Unit, + onDone: () -> Unit, ) { val cs = composeState.value val textColor = MaterialTheme.colors.onBackground diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.android.kt index dc56ead544..38dd78dd99 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.android.kt @@ -8,6 +8,8 @@ import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.compose.ui.window.PopupProperties +actual fun Modifier.onRightClick(action: () -> Unit): Modifier = this + actual interface DefaultExposedDropdownMenuBoxScope { @Composable actual fun DefaultExposedDropdownMenu( diff --git a/apps/multiplatform/common/src/commonMain/cpp/desktop/CMakeLists.txt b/apps/multiplatform/common/src/commonMain/cpp/desktop/CMakeLists.txt index 2f8f119d75..849a6c98a7 100644 --- a/apps/multiplatform/common/src/commonMain/cpp/desktop/CMakeLists.txt +++ b/apps/multiplatform/common/src/commonMain/cpp/desktop/CMakeLists.txt @@ -19,6 +19,8 @@ project("app") if(UNIX AND NOT APPLE) set(OS_LIB_PATH "linux") set(OS_LIB_EXT "so") + # Makes ld search libs in the same dir as libapp-lib, not in system dirs + set(CMAKE_BUILD_RPATH "$ORIGIN") elseif(WIN32) set(OS_LIB_PATH "windows") set(OS_LIB_EXT "dll") @@ -37,9 +39,6 @@ else() set(OS_LIB_ARCH "${CMAKE_SYSTEM_PROCESSOR}") endif() -# Makes ld search libs in the same dir as libapp-lib, not in system dirs -#set(CMAKE_BUILD_RPATH "$ORIGIN") - # Creates and names a library, sets it as either STATIC # or SHARED, and provides the relative paths to its source code. # You can define multiple libraries, and CMake builds them for you. diff --git a/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c b/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c index 2f7299393c..2c3e123fd5 100644 --- a/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c +++ b/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c @@ -1,4 +1,5 @@ #include +#include // from the RTS void hs_init(int * argc, char **argv[]); @@ -30,13 +31,35 @@ extern char *chat_parse_markdown(const char *str); extern char *chat_parse_server(const char *str); extern char *chat_password_hash(const char *pwd, const char *salt); + +// As a reference: https://stackoverflow.com/a/60002045 +jstring correct_string_utf8(JNIEnv *env, char *string) { + jobject bb = (*env)->NewDirectByteBuffer(env, (void *)string, strlen(string)); + jclass cls_charset = (*env)->FindClass(env, "java/nio/charset/Charset"); + jmethodID mid_charset_forName = (*env)->GetStaticMethodID(env, cls_charset, "forName", "(Ljava/lang/String;)Ljava/nio/charset/Charset;"); + jobject charset = (*env)->CallStaticObjectMethod(env, cls_charset, mid_charset_forName, (*env)->NewStringUTF(env, "UTF-8")); + + jmethodID mid_decode = (*env)->GetMethodID(env, cls_charset, "decode", "(Ljava/nio/ByteBuffer;)Ljava/nio/CharBuffer;"); + jobject cb = (*env)->CallObjectMethod(env, charset, mid_decode, bb); + + jclass cls_char_buffer = (*env)->FindClass(env, "java/nio/CharBuffer"); + jmethodID mid_to_string = (*env)->GetMethodID(env, cls_char_buffer, "toString", "()Ljava/lang/String;"); + jstring res = (*env)->CallObjectMethod(env, cb, mid_to_string); + + (*env)->DeleteLocalRef(env, bb); + (*env)->DeleteLocalRef(env, charset); + (*env)->DeleteLocalRef(env, cb); + return res; +} + + JNIEXPORT jobjectArray JNICALL Java_chat_simplex_common_platform_CoreKt_chatMigrateInit(JNIEnv *env, jclass clazz, jstring dbPath, jstring dbKey, jstring confirm) { const char *_dbPath = (*env)->GetStringUTFChars(env, dbPath, JNI_FALSE); const char *_dbKey = (*env)->GetStringUTFChars(env, dbKey, JNI_FALSE); const char *_confirm = (*env)->GetStringUTFChars(env, confirm, JNI_FALSE); jlong _ctrl = (jlong) 0; - jstring res = (*env)->NewStringUTF(env, chat_migrate_init(_dbPath, _dbKey, _confirm, &_ctrl)); + jstring res = correct_string_utf8(env, chat_migrate_init(_dbPath, _dbKey, _confirm, &_ctrl)); (*env)->ReleaseStringUTFChars(env, dbPath, _dbPath); (*env)->ReleaseStringUTFChars(env, dbKey, _dbKey); (*env)->ReleaseStringUTFChars(env, dbKey, _confirm); @@ -56,25 +79,25 @@ Java_chat_simplex_common_platform_CoreKt_chatMigrateInit(JNIEnv *env, jclass cla JNIEXPORT jstring JNICALL Java_chat_simplex_common_platform_CoreKt_chatSendCmd(JNIEnv *env, jclass clazz, jlong controller, jstring msg) { const char *_msg = (*env)->GetStringUTFChars(env, msg, JNI_FALSE); - jstring res = (*env)->NewStringUTF(env, chat_send_cmd((void*)controller, _msg)); + jstring res = correct_string_utf8(env, chat_send_cmd((void*)controller, _msg)); (*env)->ReleaseStringUTFChars(env, msg, _msg); return res; } JNIEXPORT jstring JNICALL Java_chat_simplex_common_platform_CoreKt_chatRecvMsg(JNIEnv *env, jclass clazz, jlong controller) { - return (*env)->NewStringUTF(env, chat_recv_msg((void*)controller)); + return correct_string_utf8(env, chat_recv_msg((void*)controller)); } JNIEXPORT jstring JNICALL Java_chat_simplex_common_platform_CoreKt_chatRecvMsgWait(JNIEnv *env, jclass clazz, jlong controller, jint wait) { - return (*env)->NewStringUTF(env, chat_recv_msg_wait((void*)controller, wait)); + return correct_string_utf8(env, chat_recv_msg_wait((void*)controller, wait)); } JNIEXPORT jstring JNICALL Java_chat_simplex_common_platform_CoreKt_chatParseMarkdown(JNIEnv *env, jclass clazz, jstring str) { const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE); - jstring res = (*env)->NewStringUTF(env, chat_parse_markdown(_str)); + jstring res = correct_string_utf8(env, chat_parse_markdown(_str)); (*env)->ReleaseStringUTFChars(env, str, _str); return res; } @@ -82,7 +105,7 @@ Java_chat_simplex_common_platform_CoreKt_chatParseMarkdown(JNIEnv *env, jclass c JNIEXPORT jstring JNICALL Java_chat_simplex_common_platform_CoreKt_chatParseServer(JNIEnv *env, jclass clazz, jstring str) { const char *_str = (*env)->GetStringUTFChars(env, str, JNI_FALSE); - jstring res = (*env)->NewStringUTF(env, chat_parse_server(_str)); + jstring res = correct_string_utf8(env, chat_parse_server(_str)); (*env)->ReleaseStringUTFChars(env, str, _str); return res; } @@ -91,7 +114,7 @@ JNIEXPORT jstring JNICALL Java_chat_simplex_common_platform_CoreKt_chatPasswordHash(JNIEnv *env, jclass clazz, jstring pwd, jstring salt) { const char *_pwd = (*env)->GetStringUTFChars(env, pwd, JNI_FALSE); const char *_salt = (*env)->GetStringUTFChars(env, salt, JNI_FALSE); - jstring res = (*env)->NewStringUTF(env, chat_password_hash(_pwd, _salt)); + jstring res = correct_string_utf8(env, chat_password_hash(_pwd, _salt)); (*env)->ReleaseStringUTFChars(env, pwd, _pwd); (*env)->ReleaseStringUTFChars(env, salt, _salt); return res; diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt index fdc1969411..7228f4ebf4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/AppLock.kt @@ -6,8 +6,7 @@ import androidx.compose.material.Surface import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.Modifier import chat.simplex.common.model.* -import chat.simplex.common.platform.Log -import chat.simplex.common.platform.TAG +import chat.simplex.common.platform.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.localauth.SetAppPasscodeView import chat.simplex.common.views.usersettings.* @@ -41,17 +40,25 @@ object AppLock { text = generalGetString(MR.strings.la_notice_to_protect_your_information_turn_on_simplex_lock_you_will_be_prompted_to_complete_authentication_before_this_feature_is_enabled), confirmText = generalGetString(MR.strings.la_notice_turn_on), onConfirm = { + laNoticeShown.set(true) withBGApi { // to remove this call, change ordering of onConfirm call in AlertManager - showChooseLAMode(laNoticeShown) + if (appPlatform.isAndroid) { + showChooseLAMode() + } else { + AlertManager.shared.hideAlert() + setPasscode() + } } + }, + onDismiss = { + AlertManager.shared.hideAlert() } ) } } - private fun showChooseLAMode(laNoticeShown: SharedPreference) { + private fun showChooseLAMode() { Log.d(TAG, "showLANotice") - laNoticeShown.set(true) AlertManager.shared.showAlertDialogStacked( title = generalGetString(MR.strings.la_lock_mode), text = null, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 3189e1c705..220d717585 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -56,7 +56,7 @@ object ChatModel { val chatItems = mutableStateListOf() val groupMembers = mutableStateListOf() - val terminalItems = mutableStateListOf() + val terminalItems = mutableStateOf(emptyList()) val userAddress = mutableStateOf(null) // Allows to temporary save servers that are being edited on multiple screens val userSMPServersUnsaved = mutableStateOf<(List)?>(null) @@ -130,10 +130,11 @@ object ChatModel { } } - fun hasChat(id: String): Boolean = chats.firstOrNull { it.id == id } != null - fun getChat(id: String): Chat? = chats.firstOrNull { it.id == id } - fun getContactChat(contactId: Long): Chat? = chats.firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId } - private fun getChatIndex(id: String): Int = chats.indexOfFirst { it.id == id } + // toList() here is to prevent ConcurrentModificationException that is rarely happens but happens + fun hasChat(id: String): Boolean = chats.toList().firstOrNull { it.id == id } != null + fun getChat(id: String): Chat? = chats.toList().firstOrNull { it.id == id } + fun getContactChat(contactId: Long): Chat? = chats.toList().firstOrNull { it.chatInfo is ChatInfo.Direct && it.chatInfo.apiId == contactId } + private fun getChatIndex(id: String): Int = chats.toList().indexOfFirst { it.id == id } fun addChat(chat: Chat) = chats.add(index = 0, chat) fun updateChatInfo(cInfo: ChatInfo) { @@ -483,10 +484,11 @@ object ChatModel { networkStatuses[contact.activeConn.agentConnId] ?: NetworkStatus.Unknown() fun addTerminalItem(item: TerminalItem) { - if (terminalItems.size >= 500) { - terminalItems.removeAt(0) + if (terminalItems.value.size >= 500) { + terminalItems.value = terminalItems.value.takeLast(499) + item + } else { + terminalItems.value += item } - terminalItems.add(item) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt index ed35392159..fff77ee23b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt @@ -22,8 +22,6 @@ val appVersionInfo: Pair = if (appPlatform == AppPlatform.ANDROID) else BuildConfigCommon.DESKTOP_VERSION_NAME to null -expect fun initHaskell() - class FifoQueue(private var capacity: Int) : LinkedList() { override fun add(element: E): Boolean { if(size > capacity) removeFirst() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt index 6d60703e53..19c0fe01b3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/PlatformTextField.kt @@ -11,5 +11,6 @@ expect fun PlatformTextField( textStyle: MutableState, showDeleteTextButton: MutableState, userIsObserver: Boolean, - onMessageChange: (String) -> Unit + onMessageChange: (String) -> Unit, + onDone: () -> Unit, ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index 47bb6918dd..01e64d54c9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -34,7 +34,7 @@ fun TerminalView(chatModel: ChatModel, close: () -> Unit) { close() }) TerminalLayout( - remember { chatModel.terminalItems }, + chatModel.terminalItems, composeState, sendCommand = { sendCommand(chatModel, composeState) }, close @@ -62,7 +62,7 @@ private fun sendCommand(chatModel: ChatModel, composeState: MutableState, + terminalItems: MutableState>, composeState: MutableState, sendCommand: () -> Unit, close: () -> Unit @@ -115,12 +115,12 @@ fun TerminalLayout( private var lazyListState = 0 to 0 @Composable -fun TerminalLog(terminalItems: List) { +fun TerminalLog(terminalItems: MutableState>) { val listState = rememberLazyListState(lazyListState.first, lazyListState.second) DisposableEffect(Unit) { onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset } } - val reversedTerminalItems by remember { derivedStateOf { terminalItems.reversed().toList() } } + val reversedTerminalItems by remember { derivedStateOf { terminalItems.value.reversed().toList() } } val clipboard = LocalClipboardManager.current LazyColumn(state = listState, reverseLayout = true) { items(reversedTerminalItems) { item -> @@ -152,7 +152,7 @@ fun TerminalLog(terminalItems: List) { fun PreviewTerminalLayout() { SimpleXTheme { TerminalLayout( - terminalItems = TerminalItem.sampleData, + terminalItems = remember { mutableStateOf(TerminalItem.sampleData) }, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = false)) }, sendCommand = {}, close = {} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt index f6b3c899ae..1a2f5df573 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt @@ -3,6 +3,7 @@ package chat.simplex.common.views.call import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* import chat.simplex.common.views.helpers.withApi +import chat.simplex.common.views.usersettings.showInDevelopingAlert import kotlinx.datetime.Clock import kotlin.time.Duration.Companion.minutes @@ -24,6 +25,9 @@ class CallManager(val chatModel: ChatModel) { } fun acceptIncomingCall(invitation: RcvCallInvitation) { + if (appPlatform.isDesktop) { + return showInDevelopingAlert() + } val call = chatModel.activeCall.value if (call == null) { justAcceptIncomingCall(invitation = invitation) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index 800c2e0a44..0ab8c4633a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -70,6 +70,7 @@ fun ChatItemInfoView(ci: ChatItem, ciInfo: ChatItemInfo, devTools: Boolean) { Box( Modifier.clip(RoundedCornerShape(18.dp)).background(itemColor).padding(bottom = 3.dp) .combinedClickable(onLongClick = { showMenu.value = true }, onClick = {}) + .onRightClick { showMenu.value = true } ) { Box(Modifier.padding(vertical = 6.dp, horizontal = 12.dp)) { TextBubble(text, ciVersion.formattedText, sender = null, showMenu) @@ -116,6 +117,7 @@ fun ChatItemInfoView(ci: ChatItem, ciInfo: ChatItemInfo, devTools: Boolean) { Box( Modifier.clip(RoundedCornerShape(18.dp)).background(quoteColor).padding(bottom = 3.dp) .combinedClickable(onLongClick = { showMenu.value = true }, onClick = {}) + .onRightClick { showMenu.value = true } ) { Box(Modifier.padding(vertical = 6.dp, horizontal = 12.dp)) { TextBubble(text, qi.formattedText, sender = qi.sender(null), showMenu) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 84a9d02bfd..209d1a80a9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -34,6 +34,7 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.model.GroupInfo import chat.simplex.common.platform.* import chat.simplex.common.platform.AudioPlayer +import chat.simplex.common.views.usersettings.showInDevelopingAlert import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.coroutines.flow.* @@ -236,7 +237,10 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: () -> Unit) { joinGroup = { groupId -> withApi { chatModel.controller.apiJoinGroup(groupId) } }, - startCall = { media -> + startCall = out@ { media -> + if (appPlatform.isDesktop) { + return@out showInDevelopingAlert() + } val cInfo = chat.chatInfo if (cInfo is ChatInfo.Direct) { chatModel.activeCall.value = Call(contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt index af8f4520b2..a9ca677df9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/SendMsgView.kt @@ -24,6 +24,7 @@ import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.helpers.* import chat.simplex.common.model.ChatItem import chat.simplex.common.platform.* +import chat.simplex.common.views.usersettings.showInDevelopingAlert import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource import dev.icerock.moko.resources.compose.painterResource @@ -66,7 +67,9 @@ fun SendMsgView( val showVoiceButton = cs.message.isEmpty() && showVoiceRecordIcon && !composeState.value.editing && cs.liveMessage == null && (cs.preview is ComposePreview.NoPreview || recState.value is RecordingState.Started) val showDeleteTextButton = rememberSaveable { mutableStateOf(false) } - PlatformTextField(composeState, textStyle, showDeleteTextButton, userIsObserver, onMessageChange) + PlatformTextField(composeState, textStyle, showDeleteTextButton, userIsObserver, onMessageChange) { + sendMessage(null) + } // Disable clicks on text field if (cs.preview is ComposePreview.VoicePreview || !userCanSend || cs.inProgress) { Box( @@ -320,7 +323,10 @@ private fun RecordVoiceView(recState: MutableState, stopRecOnNex LockToCurrentOrientationUntilDispose() StopRecordButton(stopRecordingAndAddAudio) } else { - val startRecording: () -> Unit = { + val startRecording: () -> Unit = out@ { + if (appPlatform.isDesktop) { + return@out showInDevelopingAlert() + } recState.value = RecordingState.Started( filePath = rec.start { progress: Int?, finished: Boolean -> val state = recState.value @@ -446,7 +452,8 @@ private fun SendMsgButton( role = Role.Button, interactionSource = interactionSource, indication = rememberRipple(bounded = false, radius = 24.dp) - ), + ) + .onRightClick { onLongClick?.invoke() }, contentAlignment = Alignment.Center ) { Icon( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt index 6430a23db8..605a95d6d8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIImageView.kt @@ -1,7 +1,6 @@ package chat.simplex.common.views.chat.item -import androidx.compose.foundation.Image -import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon @@ -101,7 +100,8 @@ fun CIImageView( .combinedClickable( onLongClick = { showMenu.value = true }, onClick = onClick - ), + ) + .onRightClick { showMenu.value = true }, contentScale = ContentScale.FillWidth, ) } @@ -118,7 +118,8 @@ fun CIImageView( .combinedClickable( onLongClick = { showMenu.value = true }, onClick = onClick - ), + ) + .onRightClick { showMenu.value = true }, contentScale = ContentScale.FillWidth, ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt index 22d89e767c..95bd6557a6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVIdeoView.kt @@ -142,7 +142,8 @@ private fun BoxScope.PlayButton(error: Boolean = false, onLongClick: () -> Unit, Box( Modifier .defaultMinSize(minWidth = 40.dp, minHeight = 40.dp) - .combinedClickable(onClick = onClick, onLongClick = onLongClick), + .combinedClickable(onClick = onClick, onLongClick = onLongClick) + .onRightClick { onLongClick.invoke() }, contentAlignment = Alignment.Center ) { Icon( @@ -211,7 +212,8 @@ private fun ImageView(preview: ImageBitmap, showMenu: MutableState, onC .combinedClickable( onLongClick = { showMenu.value = true }, onClick = onClick - ), + ) + .onRightClick { showMenu.value = true }, contentScale = ContentScale.FillWidth, ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt index cb557b0516..9374225897 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt @@ -222,7 +222,8 @@ private fun PlayPauseButton( .combinedClickable( onClick = { if (!audioPlaying) play() else pause() }, onLongClick = longClick - ), + ) + .onRightClick { longClick() }, contentAlignment = Alignment.Center ) { Icon( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index 2a44d7d8ec..188b89efd4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -111,7 +111,8 @@ fun ChatItemView( Column( Modifier .clip(RoundedCornerShape(18.dp)) - .combinedClickable(onLongClick = { showMenu.value = true }, onClick = onClick), + .combinedClickable(onLongClick = { showMenu.value = true }, onClick = onClick) + .onRightClick { showMenu.value = true }, ) { @Composable fun framedItemView() { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index 18a14efe34..710437c4f2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -10,7 +10,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.layout.* import androidx.compose.ui.platform.UriHandler @@ -23,10 +22,8 @@ import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* -import chat.simplex.common.model.* import chat.simplex.common.platform.base64ToBitmap import chat.simplex.res.MR -import kotlinx.datetime.Clock import kotlin.math.min @Composable @@ -110,6 +107,7 @@ fun FramedItemView( onLongClick = { showMenu.value = true }, onClick = { scrollToItem(qi.itemId?: return@combinedClickable) } ) + .onRightClick { showMenu.value = true } ) { when (qi.content) { is MsgContent.MCImage -> { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt index 07c5db4d71..24aad7fdd8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt @@ -19,8 +19,7 @@ import chat.simplex.common.model.* import chat.simplex.common.platform.Log import chat.simplex.common.platform.TAG import chat.simplex.common.ui.theme.CurrentColors -import chat.simplex.common.views.helpers.DisposableEffectOnGone -import chat.simplex.common.views.helpers.detectGesture +import chat.simplex.common.views.helpers.* import kotlinx.coroutines.* val reserveTimestampStyle = SpanStyle(color = Color.Transparent) @@ -149,7 +148,7 @@ fun MarkdownText ( } else { ft.format.style } - withAnnotation(tag = "URL", annotation = link) { + withAnnotation(tag = if (ft.format is Format.SimplexLink && linkMode != SimplexLinkMode.BROWSER) "SIMPLEX_URL" else "URL", annotation = link) { withStyle(ftStyle) { append(ft.viewText(linkMode)) } } } else { @@ -170,6 +169,8 @@ fun MarkdownText ( onLongClick = { offset -> annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset) .firstOrNull()?.let { annotation -> onLinkLongClick(annotation.item) } + annotatedText.getStringAnnotations(tag = "SIMPLEX_URL", start = offset, end = offset) + .firstOrNull()?.let { annotation -> onLinkLongClick(annotation.item) } }, onClick = { offset -> annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset) @@ -182,9 +183,14 @@ fun MarkdownText ( Log.e(TAG, "Open url: ${e.stackTraceToString()}") } } + annotatedText.getStringAnnotations(tag = "SIMPLEX_URL", start = offset, end = offset) + .firstOrNull()?.let { annotation -> + uriHandler.openVerifiedSimplexUri(annotation.item) + } }, shouldConsumeEvent = { offset -> annotatedText.getStringAnnotations(tag = "URL", start = offset, end = offset).any() + annotatedText.getStringAnnotations(tag = "SIMPLEX_URL", start = offset, end = offset).any() } ) } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatHelpView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatHelpView.kt index 0505f58dc2..866ad04b02 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatHelpView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatHelpView.kt @@ -28,7 +28,7 @@ fun ChatHelpView(addContact: (() -> Unit)? = null) { verticalArrangement = Arrangement.spacedBy(10.dp) ) { Text(stringResource(MR.strings.thank_you_for_installing_simplex), lineHeight = 22.sp) - ReadableTextWithLink(MR.strings.you_can_connect_to_simplex_chat_founder, simplexTeamUri) + ReadableTextWithLink(MR.strings.you_can_connect_to_simplex_chat_founder, simplexTeamUri, simplexLink = true) Column( Modifier.padding(top = 24.dp), verticalArrangement = Arrangement.spacedBy(10.dp) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt index 73cb007898..0be9ef12d8 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListNavLinkView.kt @@ -24,7 +24,6 @@ import chat.simplex.common.views.chat.item.InvalidJSONView import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.ContactConnectionInfoView -import chat.simplex.common.model.* import chat.simplex.common.platform.appPlatform import chat.simplex.common.platform.ntfManager import chat.simplex.res.MR @@ -619,7 +618,9 @@ fun ChatListNavLinkLayout( stopped: Boolean ) { var modifier = Modifier.fillMaxWidth() - if (!stopped) modifier = modifier.combinedClickable(onClick = click, onLongClick = { showMenu.value = true }) + if (!stopped) modifier = modifier + .combinedClickable(onClick = click, onLongClick = { showMenu.value = true }) + .onRightClick { showMenu.value = true } Box(modifier) { Row( modifier = Modifier diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index ec5d0071c0..43b06a2159 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.* @@ -27,9 +28,8 @@ import chat.simplex.common.views.usersettings.simplexTeamUri import chat.simplex.common.platform.* import chat.simplex.common.views.newchat.* import chat.simplex.res.MR -import kotlinx.coroutines.delay +import kotlinx.coroutines.* import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch import java.net.URI @Composable @@ -130,7 +130,7 @@ private fun OnboardingButtons(openNewChatSheet: () -> Unit) { Column(Modifier.fillMaxSize().padding(DEFAULT_PADDING), horizontalAlignment = Alignment.End, verticalArrangement = Arrangement.Bottom) { val uriHandler = LocalUriHandler.current ConnectButton(generalGetString(MR.strings.chat_with_developers)) { - uriHandler.openUriCatching(simplexTeamUri) + uriHandler.openVerifiedSimplexUri(simplexTeamUri) } Spacer(Modifier.height(DEFAULT_PADDING)) ConnectButton(generalGetString(MR.strings.tap_to_start_new_chat), openNewChatSheet) @@ -344,7 +344,11 @@ private fun ChatList(chatModel: ChatModel, search: String) { onDispose { lazyListState = listState.firstVisibleItemIndex to listState.firstVisibleItemScrollOffset } } val showUnreadAndFavorites = remember { ChatController.appPrefs.showUnreadAndFavorites.state }.value - val chats by remember(search, showUnreadAndFavorites) { derivedStateOf { filteredChats(showUnreadAndFavorites, search) } } + val allChats = remember { chatModel.chats } + // In some not always reproducible situations this code produce IndexOutOfBoundsException on Compose's side + // which is related to [derivedStateOf]. Using safe alternative instead + // val chats by remember(search, showUnreadAndFavorites) { derivedStateOf { filteredChats(showUnreadAndFavorites, search, allChats.toList()) } } + val chats = filteredChats(showUnreadAndFavorites, search, allChats.toList()) LazyColumn( modifier = Modifier.fillMaxWidth(), listState @@ -360,13 +364,12 @@ private fun ChatList(chatModel: ChatModel, search: String) { } } -private fun filteredChats(showUnreadAndFavorites: Boolean, searchText: String): List { - val chatModel = ChatModel +private fun filteredChats(showUnreadAndFavorites: Boolean, searchText: String, chats: List): List { val s = searchText.trim().lowercase() return if (s.isEmpty() && !showUnreadAndFavorites) - chatModel.chats + chats else { - chatModel.chats.filter { chat -> + chats.filter { chat -> when (val cInfo = chat.chatInfo) { is ChatInfo.Direct -> if (s.isEmpty()) { filtered(chat) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt index 5381ebc283..139b23e818 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/UserPicker.kt @@ -165,6 +165,7 @@ fun UserProfilePickerItem(u: User, unreadCount: Int = 0, padding: PaddingValues interactionSource = remember { MutableInteractionSource() }, indication = if (!u.activeUser) LocalIndication.current else null ) + .onRightClick { onLongClick() } .padding(padding), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt index 0ae69d82f6..f3aec77e0b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultDropdownMenu.kt @@ -11,6 +11,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp +expect fun Modifier.onRightClick(action: () -> Unit): Modifier + expect interface DefaultExposedDropdownMenuBoxScope { @Composable open fun DefaultExposedDropdownMenu( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt index 377eb7a8ae..e59653cef1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt @@ -1,5 +1,4 @@ -import androidx.compose.foundation.clickable -import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.* @@ -15,8 +14,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* import chat.simplex.common.platform.screenWidth import chat.simplex.common.ui.theme.* -import chat.simplex.common.views.helpers.ValueTitleDesc -import chat.simplex.common.views.helpers.ValueTitle +import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.SettingsActionItemWithContent import chat.simplex.res.MR @@ -132,7 +130,9 @@ fun SectionItemViewSpaceBetween( .fillMaxWidth() .sizeIn(minHeight = minHeight) Row( - if (click == null || disabled) modifier.padding(padding) else modifier.combinedClickable(onClick = click, onLongClick = onLongClick).padding(padding), + if (click == null || disabled) modifier.padding(padding) else modifier + .combinedClickable(onClick = click, onLongClick = onLongClick).padding(padding) + .onRightClick { onLongClick?.invoke() }, horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index ec25a8811c..781adbd8e0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.unit.* import chat.simplex.common.model.* import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.ThemeOverrides +import chat.simplex.common.views.chatlist.connectIfOpenedViaUri import chat.simplex.res.MR import com.charleskorn.kaml.decodeFromStream import dev.icerock.moko.resources.StringResource @@ -281,6 +282,13 @@ inline fun serializableSaver(): Saver = Saver( restore = { json.decodeFromString(it) } ) +fun UriHandler.openVerifiedSimplexUri(uri: String) { + val URI = try { URI.create(uri) } catch (e: Exception) { null } + if (URI != null) { + connectIfOpenedViaUri(URI, ChatModel) + } +} + fun UriHandler.openUriCatching(uri: String) { try { openUri(uri) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt index b8eb064b8c..84d1ae639d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt @@ -57,8 +57,13 @@ fun CreateSimpleXAddress(m: ChatModel) { } }, nextStep = { - m.controller.appPrefs.onboardingStage.set(OnboardingStage.Step4_SetNotificationsMode) - m.onboardingStage.value = OnboardingStage.Step4_SetNotificationsMode + val next = if (appPlatform.isAndroid) { + OnboardingStage.Step4_SetNotificationsMode + } else { + OnboardingStage.OnboardingComplete + } + m.controller.appPrefs.onboardingStage.set(next) + m.onboardingStage.value = next }, ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index 7785180926..3b2e0b408e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -55,7 +55,7 @@ fun ReadableText(stringResId: StringResource, textAlign: TextAlign = TextAlign.S } @Composable -fun ReadableTextWithLink(stringResId: StringResource, link: String, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp)) { +fun ReadableTextWithLink(stringResId: StringResource, link: String, textAlign: TextAlign = TextAlign.Start, padding: PaddingValues = PaddingValues(bottom = 12.dp), simplexLink: Boolean = false) { val annotated = annotatedStringResource(stringResId) val primary = MaterialTheme.colors.primary // This replaces links in text highlighted with specific color, e.g. SimplexBlue @@ -71,7 +71,7 @@ fun ReadableTextWithLink(stringResId: StringResource, link: String, textAlign: T newStyles } val uriHandler = LocalUriHandler.current - Text(AnnotatedString(annotated.text, newStyles), modifier = Modifier.padding(padding).clickable { uriHandler.openUriCatching(link) }, textAlign = textAlign, lineHeight = 22.sp) + Text(AnnotatedString(annotated.text, newStyles), modifier = Modifier.padding(padding).clickable { if (simplexLink) uriHandler.openVerifiedSimplexUri(link) else uriHandler.openUriCatching(link) }, textAlign = textAlign, lineHeight = 22.sp) } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index 1e69d92c22..8248194ebc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -41,6 +41,7 @@ fun SimpleXInfoLayout( .fillMaxSize() .verticalScroll(rememberScrollState()) .padding(start = DEFAULT_PADDING , end = DEFAULT_PADDING, top = DEFAULT_PADDING), + horizontalAlignment = Alignment.CenterHorizontally ) { Box(Modifier.fillMaxWidth().padding(top = 8.dp, bottom = 10.dp), contentAlignment = Alignment.Center) { SimpleXLogo() @@ -48,9 +49,11 @@ fun SimpleXInfoLayout( Text(stringResource(MR.strings.next_generation_of_private_messaging), style = MaterialTheme.typography.h2, modifier = Modifier.padding(bottom = 48.dp).padding(horizontal = 36.dp), textAlign = TextAlign.Center) - InfoRow(painterResource(MR.images.privacy), MR.strings.privacy_redefined, MR.strings.first_platform_without_user_ids, width = 80.dp) - InfoRow(painterResource(MR.images.shield), MR.strings.immune_to_spam_and_abuse, MR.strings.people_can_connect_only_via_links_you_share) - InfoRow(painterResource(if (isInDarkTheme()) MR.images.decentralized_light else MR.images.decentralized), MR.strings.decentralized, MR.strings.opensource_protocol_and_code_anybody_can_run_servers) + Column { + InfoRow(painterResource(MR.images.privacy), MR.strings.privacy_redefined, MR.strings.first_platform_without_user_ids, width = 80.dp) + InfoRow(painterResource(MR.images.shield), MR.strings.immune_to_spam_and_abuse, MR.strings.people_can_connect_only_via_links_you_share) + InfoRow(painterResource(if (isInDarkTheme()) MR.images.decentralized_light else MR.images.decentralized), MR.strings.decentralized, MR.strings.opensource_protocol_and_code_anybody_can_run_servers) + } Spacer(Modifier.fillMaxHeight().weight(1f)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt index c69ba20939..68aa1fff9e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.platform.LocalUriHandler import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.appPlatform import chat.simplex.common.views.TerminalView import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @@ -41,3 +42,10 @@ fun DeveloperView( SectionBottomSpacer() } } + +fun showInDevelopingAlert() { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.in_developing_title), + text = generalGetString(MR.strings.in_developing_desc) + ) +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index fda24031aa..0120e35700 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -175,7 +175,7 @@ fun SettingsLayout( SettingsActionItem(painterResource(MR.images.ic_help), stringResource(MR.strings.how_to_use_simplex_chat), showModal { HelpView(userDisplayName) }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_add), stringResource(MR.strings.whats_new), showCustomModal { _, close -> WhatsNewView(viaSettings = true, close) }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_info), stringResource(MR.strings.about_simplex_chat), showModal { SimpleXInfo(it, onboarding = false) }, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openUriCatching(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_tag), stringResource(MR.strings.chat_with_the_founder), { uriHandler.openVerifiedSimplexUri(simplexTeamUri) }, textColor = MaterialTheme.colors.primary, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_mail), stringResource(MR.strings.send_us_an_email), { uriHandler.openUriCatching("mailto:chat@simplex.chat") }, textColor = MaterialTheme.colors.primary, extraPadding = true) } SectionDividerSpaced() diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index ac7451c389..9ed0c6fbd4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -1526,4 +1526,8 @@ Delivery receipts are disabled! You can enable them later via app Privacy & Security settings. Error enabling delivery receipts! + + + Coming soon! + This feature is not yet supported. Try the next release. diff --git a/apps/multiplatform/common/src/commonMain/resources/distribute/simplex.png b/apps/multiplatform/common/src/commonMain/resources/distribute/simplex.png deleted file mode 100644 index 6ab60a3293..0000000000 Binary files a/apps/multiplatform/common/src/commonMain/resources/distribute/simplex.png and /dev/null differ diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index 936942d980..81e506a258 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.window.* import chat.simplex.common.model.ChatController import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.desktopPlatform import chat.simplex.common.ui.theme.SimpleXTheme import chat.simplex.common.views.helpers.FileDialogChooser import kotlinx.coroutines.* @@ -24,7 +25,10 @@ import java.io.File val simplexWindowState = SimplexWindowState() fun showApp() = application { - val windowState = rememberWindowState(placement = WindowPlacement.Floating, width = 1366.dp, height = 768.dp) + // For some reason on Linux actual width will be 10.dp less after specifying it here. If we specify 1366, + // it will show 1356. But after that we can still update it to 1366 by changing window state. Just making it +10 now here + val width = if (desktopPlatform.isLinux()) 1376.dp else 1366.dp + val windowState = rememberWindowState(placement = WindowPlacement.Floating, width = width, height = 768.dp) simplexWindowState.windowState = windowState Window(state = windowState, onCloseRequest = ::exitApplication, onKeyEvent = { if (it.key == Key.Escape && it.type == KeyEventType.KeyUp) { @@ -146,6 +150,3 @@ fun AppPreview() { AppScreen() } } - -/** Needed for [chat.simplex.common.platform.Files] to get path to jar file */ -class DesktopApp() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt index 7c9201e4e1..54f73c3f52 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt @@ -1,12 +1,8 @@ package chat.simplex.common.platform -import chat.simplex.common.DesktopApp import chat.simplex.common.model.* import chat.simplex.common.views.call.RcvCallInvitation import chat.simplex.common.views.helpers.withBGApi -import java.io.* -import java.nio.file.* -import java.nio.file.attribute.BasicFileAttributes import java.util.* actual val appPlatform = AppPlatform.DESKTOP @@ -39,29 +35,3 @@ private fun applyAppLocale() { if (lang == null || lang == Locale.getDefault().language) return Locale.setDefault(Locale.forLanguageTag(lang)) } - -@Suppress("UnsafeDynamicallyLoadedCode") -actual fun initHaskell() { - val libApp = "libapp-lib.${desktopPlatform.libExtension}" - val libsTmpDir = File(tmpDir.absolutePath + File.separator + "libs") - copyResources(desktopPlatform.libPath, libsTmpDir.toPath()) - System.load(File(libsTmpDir, libApp).absolutePath) - libsTmpDir.deleteRecursively() - initHS() -} - -private fun copyResources(from: String, to: Path) { - val resource = DesktopApp::class.java.getResource("")!!.toURI() - val fileSystem = FileSystems.newFileSystem(resource, emptyMap()) - val resPath = fileSystem.getPath(from) - Files.walkFileTree(resPath, object: SimpleFileVisitor() { - override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult { - Files.createDirectories(to.resolve(resPath.relativize(dir).toString())) - return FileVisitResult.CONTINUE - } - override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { - Files.copy(file, to.resolve(resPath.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING) - return FileVisitResult.CONTINUE - } - }) -} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt index 023683fece..5cec4f2a77 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt @@ -7,15 +7,6 @@ import chat.simplex.res.MR import java.io.* import java.net.URI -private fun applicationParentPath(): String = try { - DesktopApp::class.java.protectionDomain!!.codeSource.location.toURI().path - .replaceAfterLast("/", "") - .replaceAfterLast(File.separator, "") - .replace("/", File.separator) -} catch (e: Exception) { - "./" -} - actual val dataDir: File = File(desktopPlatform.dataPath) actual val tmpDir: File = File(System.getProperty("java.io.tmpdir") + File.separator + "simplex").also { it.deleteOnExit() } actual val filesDir: File = File(dataDir.absolutePath + File.separator + "simplex_v1_files") diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt index 0a49ed5beb..8fabcf8285 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Platform.desktop.kt @@ -14,6 +14,8 @@ enum class DesktopPlatform(val libPath: String, val libExtension: String, val co WINDOWS_X86_64("/libs/windows-x86_64", "dll", System.getenv("AppData") + File.separator + "SimpleX", System.getenv("AppData") + File.separator + "SimpleX"), MAC_X86_64("/libs/mac-x86_64", "dylib", unixConfigPath, unixDataPath), MAC_AARCH64("/libs/mac-aarch64", "dylib", unixConfigPath, unixDataPath); + + fun isLinux() = this == LINUX_X86_64 || this == LINUX_AARCH64 } private fun detectDesktopPlatform(): DesktopPlatform { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt index ea806c0622..a8278f5331 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/PlatformTextField.desktop.kt @@ -14,10 +14,13 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.input.key.* import androidx.compose.ui.platform.* +import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import chat.simplex.common.views.chat.* @@ -33,7 +36,8 @@ actual fun PlatformTextField( textStyle: MutableState, showDeleteTextButton: MutableState, userIsObserver: Boolean, - onMessageChange: (String) -> Unit + onMessageChange: (String) -> Unit, + onDone: () -> Unit, ) { val cs = composeState.value val focusRequester = remember { FocusRequester() } @@ -47,11 +51,14 @@ actual fun PlatformTextField( keyboard?.show() } val isRtl = remember(cs.message) { isRtl(cs.message.subSequence(0, min(50, cs.message.length))) } + var textFieldValueState by remember { mutableStateOf(TextFieldValue(text = cs.message)) } + val textFieldValue = textFieldValueState.copy(text = cs.message) BasicTextField( - value = cs.message, + value = textFieldValue, onValueChange = { - if (!composeState.value.inProgress && !(composeState.value.preview is ComposePreview.VoicePreview && it != "")) { - onMessageChange(it) + if (!composeState.value.inProgress && !(composeState.value.preview is ComposePreview.VoicePreview && it.text != "")) { + textFieldValueState = it + onMessageChange(it.text) } }, textStyle = textStyle.value, @@ -60,7 +67,24 @@ actual fun PlatformTextField( capitalization = KeyboardCapitalization.Sentences, autoCorrect = true ), - modifier = Modifier.padding(vertical = 4.dp).focusRequester(focusRequester), + modifier = Modifier + .padding(vertical = 4.dp) + .focusRequester(focusRequester) + .onPreviewKeyEvent { + if (it.key == Key.Enter && it.type == KeyEventType.KeyDown) { + if (it.isShiftPressed) { + val newText = textFieldValue.text + "\n" + textFieldValueState = textFieldValue.copy( + text = newText, + selection = TextRange(newText.length, newText.length) + ) + onMessageChange(newText) + } else if (cs.message.isNotEmpty()) { + onDone() + } + true + } else false + }, cursorBrush = SolidColor(MaterialTheme.colors.secondary), decorationBox = { innerTextField -> Surface( diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt index 7e2ab9d5d0..2ba6f3b3f6 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/RecAndPlay.desktop.kt @@ -2,6 +2,7 @@ package chat.simplex.common.platform import androidx.compose.runtime.MutableState import chat.simplex.common.model.ChatItem +import chat.simplex.common.views.usersettings.showInDevelopingAlert import kotlinx.coroutines.CoroutineScope actual class RecorderNative: RecorderInterface { @@ -18,7 +19,7 @@ actual class RecorderNative: RecorderInterface { actual object AudioPlayer: AudioPlayerInterface { override fun play(filePath: String?, audioPlaying: MutableState, progress: MutableState, duration: MutableState, resetOnEnd: Boolean) { - /*LALAL*/ + showInDevelopingAlert() } override fun stop() { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt index 0c40c1c350..90f6a593f5 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/VideoPlayer.desktop.kt @@ -1,7 +1,9 @@ package chat.simplex.common.platform import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf import androidx.compose.ui.graphics.ImageBitmap +import chat.simplex.common.views.usersettings.showInDevelopingAlert import java.net.URI actual class VideoPlayer: VideoPlayerInterface { @@ -12,39 +14,40 @@ actual class VideoPlayer: VideoPlayerInterface { defaultPreview: ImageBitmap, defaultDuration: Long, soundEnabled: Boolean - ): VideoPlayer = VideoPlayer() - actual fun enableSound(enable: Boolean, fileName: String?, gallery: Boolean): Boolean { TODO() } - actual fun release(uri: URI, gallery: Boolean, remove: Boolean) { TODO() } + ): VideoPlayer = VideoPlayer().also { + it.preview.value = defaultPreview + it.duration.value = defaultDuration + it.soundEnabled.value = soundEnabled + } + actual fun enableSound(enable: Boolean, fileName: String?, gallery: Boolean): Boolean { /*TODO*/ return false } + actual fun release(uri: URI, gallery: Boolean, remove: Boolean) { /*TODO*/ } actual fun stopAll() { /*LALAL*/ } actual fun releaseAll() { /*LALAL*/ } } - override val soundEnabled: MutableState - get() = TODO("Not yet implemented") - override val brokenVideo: MutableState - get() = TODO("Not yet implemented") - override val videoPlaying: MutableState - get() = TODO("Not yet implemented") - override val progress: MutableState - get() = TODO("Not yet implemented") - override val duration: MutableState - get() = TODO("Not yet implemented") - override val preview: MutableState - get() = TODO("Not yet implemented") + override val soundEnabled: MutableState = mutableStateOf(false) + override val brokenVideo: MutableState = mutableStateOf(false) + override val videoPlaying: MutableState = mutableStateOf(false) + override val progress: MutableState = mutableStateOf(0L) + override val duration: MutableState = mutableStateOf(0L) + override val preview: MutableState = mutableStateOf(ImageBitmap(0, 0)) override fun stop() { - TODO("Not yet implemented") + /*TODO*/ } override fun play(resetOnEnd: Boolean) { - TODO("Not yet implemented") + if (appPlatform.isDesktop) { + showInDevelopingAlert() + } } override fun enableSound(enable: Boolean): Boolean { - TODO("Not yet implemented") + /*TODO*/ + return false } override fun release(remove: Boolean) { - TODO("Not yet implemented") + /*TODO*/ } } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt index 993025a115..5dfd44ba6b 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/DefaultDropDownMenu.desktop.kt @@ -1,6 +1,6 @@ package chat.simplex.common.views.helpers -import androidx.compose.foundation.clickable +import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* import androidx.compose.material.DropdownMenu @@ -9,7 +9,8 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp -import kotlin.math.exp + +actual fun Modifier.onRightClick(action: () -> Unit): Modifier = contextMenuOpenDetector { action() } actual interface DefaultExposedDropdownMenuBoxScope { @Composable diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt index 47d7829749..f4a9ac9b78 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/helpers/Utils.desktop.kt @@ -1,22 +1,88 @@ package chat.simplex.common.views.helpers import androidx.compose.ui.graphics.* -import androidx.compose.ui.text.AnnotatedString +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.CIFile import chat.simplex.common.platform.* import chat.simplex.common.simplexWindowState import java.io.File import java.net.URI -import java.util.* import javax.imageio.ImageIO import kotlin.io.encoding.Base64 import kotlin.io.encoding.ExperimentalEncodingApi import kotlin.io.path.toPath -// LALAL MAKE REALLY ANNOTATED STRING FROM HTML -actual fun escapedHtmlToAnnotatedString(text: String, density: Density): AnnotatedString { - return AnnotatedString(text) +private val bStyle = SpanStyle(fontWeight = FontWeight.Bold) +private val iStyle = SpanStyle(fontStyle = FontStyle.Italic) +private fun fontStyle(color: String) = + SpanStyle(color = Color(color.replace("#", "ff").toLongOrNull(16) ?: Color.White.toArgb().toLong())) + +actual fun escapedHtmlToAnnotatedString(text: String, density: Density): AnnotatedString = try { + buildAnnotatedString { + fun String.substringSafe(start: Int, len: Int): String = + if (start < 0 || start >= this.length || start + len < 0 || start + len > this.length) "" + else substring(start, start + len) + + var skipTil = 0 + for (outerI in text.indices) { + if (skipTil > outerI) continue + if (text[outerI] == '<') { + for (innerI in outerI + 1 until text.length) { + when { + text.substringSafe(innerI, 2) == "b>" -> { + val textStart = innerI + 2 + for (insideTagI in textStart until text.length) { + if (text[insideTagI] == '<') { + withStyle(bStyle) { append(text.substring(textStart, insideTagI)) } + skipTil = insideTagI + 4 + break + } + } + break + } + text.substringSafe(innerI, 2) == "i>" -> { + val textStart = innerI + 2 + for (insideTagI in textStart until text.length) { + if (text[insideTagI] == '<') { + withStyle(iStyle) { append(text.substring(textStart, insideTagI)) } + skipTil = insideTagI + 4 + break + } + } + break + } + text.substringSafe(innerI, 4) == "font" -> { + var textStart = innerI + 5 + var color = "#000000" + for (i in textStart until text.length) { + if (text[i] == '#') { + color = text.substring(i, i + 7) + textStart = i + 9 + break + } + } + for (insideTagI in textStart until text.length) { + if (text[insideTagI] == '<') { + withStyle(fontStyle(color)) { append(text.substring(textStart, insideTagI)) } + skipTil = insideTagI + 7 + break + } + } + break + } + } + if (skipTil > innerI) continue + } + } else { + append(text[outerI]) + } + } + } +} catch (e: Exception) { + AnnotatedString(text) } actual fun getAppFileUri(fileName: String): URI = diff --git a/apps/multiplatform/desktop/build.gradle.kts b/apps/multiplatform/desktop/build.gradle.kts index 95a4908dbd..a45fb94991 100644 --- a/apps/multiplatform/desktop/build.gradle.kts +++ b/apps/multiplatform/desktop/build.gradle.kts @@ -30,8 +30,16 @@ kotlin { compose { desktop { application { - mainClass = "MainKt" + // For debugging via VisualVM + /*jvmArgs += listOf( + "-Dcom.sun.management.jmxremote.port=8080", + "-Dcom.sun.management.jmxremote.ssl=false", + "-Dcom.sun.management.jmxremote.authenticate=false" + )*/ + mainClass = "chat.simplex.desktop.MainKt" nativeDistributions { + // For debugging via VisualVM + //modules("jdk.zipfs", "jdk.management.agent") modules("jdk.zipfs") //includeAllModules = true outputBaseDir.set(project.file("../release")) @@ -40,19 +48,19 @@ compose { //, TargetFormat.AppImage // Gradle doesn't sync on Mac with it ) linux { - iconFile.set(project.file("../common/src/commonMain/resources/distribute/simplex.png")) + iconFile.set(project.file("src/jvmMain/resources/distribute/simplex.png")) appCategory = "Messenger" } windows { - // LALAL - iconFile.set(project.file("../common/src/commonMain/resources/distribute/simplex.ico")) + packageName = "SimpleX" + iconFile.set(project.file("src/jvmMain/resources/distribute/simplex.ico")) console = true perUserInstall = true dirChooser = true } macOS { - // LALAL - //iconFile.set(project.file("../common/src/commonMain/resources/distribute/simplex.icns")) + packageName = "SimpleX" + iconFile.set(project.file("src/jvmMain/resources/distribute/simplex.icns")) appCategory = "public.app-category.social-networking" bundleID = "chat.simplex.app" } @@ -116,7 +124,7 @@ afterEvaluate { doLast { copy { from("${project(":desktop").buildDir}/cmake/main/linux-amd64", "$cppPath/desktop/libs/linux-x86_64", "$cppPath/desktop/libs/linux-x86_64/deps") - into("../common/src/commonMain/resources/libs/linux-x86_64") + into("src/jvmMain/resources/libs/linux-x86_64") include("*.so") eachFile { path = name @@ -126,7 +134,7 @@ afterEvaluate { } copy { from("${project(":desktop").buildDir}/cmake/main/linux-aarch64", "$cppPath/desktop/libs/linux-aarch64", "$cppPath/desktop/libs/linux-aarch64/deps") - into("../common/src/commonMain/resources/libs/linux-aarch64") + into("src/jvmMain/resources/libs/linux-aarch64") include("*.so") eachFile { path = name @@ -136,7 +144,7 @@ afterEvaluate { } copy { from("${project(":desktop").buildDir}/cmake/main/win-amd64", "$cppPath/desktop/libs/windows-x86_64", "$cppPath/desktop/libs/windows-x86_64/deps") - into("../common/src/commonMain/resources/libs/windows-x86_64") + into("src/jvmMain/resources/libs/windows-x86_64") include("*.dll") eachFile { path = name @@ -146,7 +154,7 @@ afterEvaluate { } copy { from("${project(":desktop").buildDir}/cmake/main/mac-x86_64", "$cppPath/desktop/libs/mac-x86_64", "$cppPath/desktop/libs/mac-x86_64/deps") - into("../common/src/commonMain/resources/libs/mac-x86_64") + into("src/jvmMain/resources/libs/mac-x86_64") include("*.dylib") eachFile { path = name @@ -156,7 +164,7 @@ afterEvaluate { } copy { from("${project(":desktop").buildDir}/cmake/main/mac-aarch64", "$cppPath/desktop/libs/mac-aarch64", "$cppPath/desktop/libs/mac-aarch64/deps") - into("../common/src/commonMain/resources/libs/mac-aarch64") + into("src/jvmMain/resources/libs/mac-aarch64") include("*.dylib") eachFile { path = name diff --git a/apps/multiplatform/desktop/src/jvmMain/kotlin/Main.kt b/apps/multiplatform/desktop/src/jvmMain/kotlin/Main.kt deleted file mode 100644 index 3b53d83a7a..0000000000 --- a/apps/multiplatform/desktop/src/jvmMain/kotlin/Main.kt +++ /dev/null @@ -1,9 +0,0 @@ -import chat.simplex.common.platform.* -import chat.simplex.common.showApp - -fun main() { - initHaskell() - initApp() - tmpDir.deleteRecursively() - return showApp() -} diff --git a/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt new file mode 100644 index 0000000000..542ade961a --- /dev/null +++ b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt @@ -0,0 +1,40 @@ +package chat.simplex.desktop + +import chat.simplex.common.platform.* +import chat.simplex.common.showApp +import java.io.File +import java.nio.file.* +import java.nio.file.attribute.BasicFileAttributes + +fun main() { + initHaskell() + initApp() + tmpDir.deleteRecursively() + return showApp() +} + +@Suppress("UnsafeDynamicallyLoadedCode") +private fun initHaskell() { + val libApp = "libapp-lib.${desktopPlatform.libExtension}" + val libsTmpDir = File(tmpDir.absolutePath + File.separator + "libs") + copyResources(desktopPlatform.libPath, libsTmpDir.toPath()) + System.load(File(libsTmpDir, libApp).absolutePath) + libsTmpDir.deleteRecursively() + initHS() +} + +private fun copyResources(from: String, to: Path) { + val resource = Class.forName("chat.simplex.desktop.MainKt").getResource("")!!.toURI() + val fileSystem = FileSystems.newFileSystem(resource, emptyMap()) + val resPath = fileSystem.getPath(from) + Files.walkFileTree(resPath, object: SimpleFileVisitor() { + override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult { + Files.createDirectories(to.resolve(resPath.relativize(dir).toString())) + return FileVisitResult.CONTINUE + } + override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult { + Files.copy(file, to.resolve(resPath.relativize(file).toString()), StandardCopyOption.REPLACE_EXISTING) + return FileVisitResult.CONTINUE + } + }) +} diff --git a/apps/multiplatform/common/src/commonMain/resources/distribute/SimpleX.desktop b/apps/multiplatform/desktop/src/jvmMain/resources/distribute/SimpleX.desktop similarity index 62% rename from apps/multiplatform/common/src/commonMain/resources/distribute/SimpleX.desktop rename to apps/multiplatform/desktop/src/jvmMain/resources/distribute/SimpleX.desktop index bcfe0c0801..225a7812ba 100644 --- a/apps/multiplatform/common/src/commonMain/resources/distribute/SimpleX.desktop +++ b/apps/multiplatform/desktop/src/jvmMain/resources/distribute/SimpleX.desktop @@ -2,6 +2,6 @@ Type=Application Name=SimpleX Chat Comment=Private and secure open-source messenger - no user IDs (not even random numbers). -Exec=simplex -Icon=simplex.png -Categories=Messenger; +Exec=/opt/simplex/bin/simplex +Icon=/opt/simplex/lib/simplex +Categories=Network;Chat; diff --git a/apps/multiplatform/desktop/src/jvmMain/resources/distribute/simplex.icns b/apps/multiplatform/desktop/src/jvmMain/resources/distribute/simplex.icns new file mode 100644 index 0000000000..c0c91a98af Binary files /dev/null and b/apps/multiplatform/desktop/src/jvmMain/resources/distribute/simplex.icns differ diff --git a/apps/multiplatform/desktop/src/jvmMain/resources/distribute/simplex.ico b/apps/multiplatform/desktop/src/jvmMain/resources/distribute/simplex.ico new file mode 100644 index 0000000000..783809be7b Binary files /dev/null and b/apps/multiplatform/desktop/src/jvmMain/resources/distribute/simplex.ico differ diff --git a/apps/multiplatform/desktop/src/jvmMain/resources/distribute/simplex.png b/apps/multiplatform/desktop/src/jvmMain/resources/distribute/simplex.png new file mode 100644 index 0000000000..d7454d4fb7 Binary files /dev/null and b/apps/multiplatform/desktop/src/jvmMain/resources/distribute/simplex.png differ diff --git a/package.yaml b/package.yaml index c4dcecd981..8dbb50ff29 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 5.2.0.4 +version: 5.3.0.0 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/scripts/desktop/build-lib-linux.sh b/scripts/desktop/build-lib-linux.sh index 0e19b5a925..41ca8a64f7 100755 --- a/scripts/desktop/build-lib-linux.sh +++ b/scripts/desktop/build-lib-linux.sh @@ -17,7 +17,7 @@ ldd libHSsimplex-chat-*-inplace-ghc${GHC_VERSION}.so | grep "ghc" | cut -d' ' -f cd - rm -rf apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ -rm -rf apps/multiplatform/common/src/commonMain/resources/libs/$OS-$ARCH/ +rm -rf apps/multiplatform/desktop/src/jvmMain/resources/libs/$OS-$ARCH/ rm -rf apps/multiplatform/desktop/build/cmake mkdir -p apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ diff --git a/scripts/desktop/build-lib-mac.sh b/scripts/desktop/build-lib-mac.sh index 59c0aeb127..a37e023130 100755 --- a/scripts/desktop/build-lib-mac.sh +++ b/scripts/desktop/build-lib-mac.sh @@ -64,7 +64,7 @@ rm deps/`basename $LIB` cd - rm -rf apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ -rm -rf apps/multiplatform/common/src/commonMain/resources/libs/$OS-$ARCH/ +rm -rf apps/multiplatform/desktop/src/jvmMain/resources/libs/$OS-$ARCH/ rm -rf apps/multiplatform/desktop/build/cmake mkdir -p apps/multiplatform/common/src/commonMain/cpp/desktop/libs/$OS-$ARCH/ diff --git a/simplex-chat.cabal b/simplex-chat.cabal index cad28ffe73..4f6ebd795f 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 5.2.0.4 +version: 5.3.0.0 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat