diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 7362e8fa46..75f7127ab5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -235,7 +235,7 @@ fun AndroidScreen(settingsState: SettingsViewState) { BoxWithConstraints { val call = remember { chatModel.activeCall} .value val showCallArea = call != null && call.callState != CallState.WaitCapabilities && call.callState != CallState.InvitationAccepted - var currentChatId by rememberSaveable { mutableStateOf(chatModel.chatId.value) } + val currentChatId = remember { mutableStateOf(chatModel.chatId.value) } val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) } Box( Modifier @@ -265,7 +265,7 @@ fun AndroidScreen(settingsState: SettingsViewState) { .distinctUntilChanged() .collect { if (it == null) onComposed(null) - currentChatId = it + currentChatId.value = it } } } @@ -273,8 +273,8 @@ fun AndroidScreen(settingsState: SettingsViewState) { .graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() } .padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp) ) Box2@{ - currentChatId?.let { - ChatView(it, chatModel, onComposed) + currentChatId.value?.let { + ChatView(currentChatId, onComposed) } } if (call != null && showCallArea) { @@ -298,7 +298,7 @@ fun StartPartOfScreen(settingsState: SettingsViewState) { @Composable fun CenterPartOfScreen() { - val currentChatId by remember { ChatModel.chatId } + val currentChatId = remember { ChatModel.chatId } LaunchedEffect(Unit) { snapshotFlow { currentChatId } .distinctUntilChanged() @@ -308,7 +308,7 @@ fun CenterPartOfScreen() { } } } - when (val id = currentChatId) { + when (currentChatId.value) { null -> { if (!rememberUpdatedState(ModalManager.center.hasModalsOpen()).value) { Box( @@ -323,7 +323,7 @@ fun CenterPartOfScreen() { ModalManager.center.showInView() } } - else -> ChatView(id, chatModel) {} + else -> ChatView(currentChatId) {} } } 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 c41a5d69f3..419e11bda1 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 @@ -415,17 +415,17 @@ object ChatModel { } } - fun markChatItemsRead(chat: Chat, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) { - val cInfo = chat.chatInfo - val markedRead = markItemsReadInCurrentChat(chat, range) + fun markChatItemsRead(remoteHostId: Long?, chatInfo: ChatInfo, range: CC.ItemRange? = null, unreadCountAfter: Int? = null) { + val cInfo = chatInfo + val markedRead = markItemsReadInCurrentChat(chatInfo, range) // update preview - val chatIdx = getChatIndex(chat.remoteHostId, cInfo.id) + val chatIdx = getChatIndex(remoteHostId, cInfo.id) if (chatIdx >= 0) { val chat = chats[chatIdx] val lastId = chat.chatItems.lastOrNull()?.id if (lastId != null) { val unreadCount = unreadCountAfter ?: if (range != null) chat.chatStats.unreadCount - markedRead else 0 - decreaseUnreadCounter(chat.remoteHostId, currentUser.value!!, chat.chatStats.unreadCount - unreadCount) + decreaseUnreadCounter(remoteHostId, currentUser.value!!, chat.chatStats.unreadCount - unreadCount) chats[chatIdx] = chat.copy( chatStats = chat.chatStats.copy( unreadCount = unreadCount, @@ -528,8 +528,8 @@ object ChatModel { } } - private fun markItemsReadInCurrentChat(chat: Chat, range: CC.ItemRange? = null): Int { - val cInfo = chat.chatInfo + private fun markItemsReadInCurrentChat(chatInfo: ChatInfo, range: CC.ItemRange? = null): Int { + val cInfo = chatInfo var markedRead = 0 if (chatId.value == cInfo.id) { var i = 0 @@ -824,14 +824,6 @@ data class Chat( val chatItems: List, val chatStats: ChatStats = ChatStats() ) { - val userCanSend: Boolean - get() = when (chatInfo) { - is ChatInfo.Direct -> true - is ChatInfo.Group -> chatInfo.groupInfo.membership.memberRole >= GroupMemberRole.Member - is ChatInfo.Local -> true - else -> false - } - val nextSendGrpInv: Boolean get() = when (chatInfo) { is ChatInfo.Direct -> chatInfo.contact.nextSendGrpInv @@ -1048,6 +1040,15 @@ sealed class ChatInfo: SomeChat, NamedChat { is ContactConnection -> contactConnection.updatedAt is InvalidJSON -> updatedAt } + + val userCanSend: Boolean + get() = when (this) { + is ChatInfo.Direct -> true + is ChatInfo.Group -> groupInfo.membership.memberRole >= GroupMemberRole.Member + is ChatInfo.Local -> true + else -> false + } + } @Serializable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index da2ead23e3..7c694e00b1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -81,7 +81,7 @@ fun ChatInfoView( sendReceipts = sendReceipts, setSendReceipts = { sendRcpts -> val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(sendRcpts = sendRcpts.bool) - updateChatSettings(chat, chatSettings, chatModel) + updateChatSettings(chat.remoteHostId, chat.chatInfo, chatSettings, chatModel) sendReceipts.value = sendRcpts }, connStats = connStats, @@ -806,7 +806,7 @@ fun MuteButton(chat: Chat, contact: Contact) { disabled = disabled, disabledLook = disabled, onClick = { - toggleNotifications(chat, !ntfsEnabled.value, chatModel, ntfsEnabled) + toggleNotifications(chat.remoteHostId, chat.chatInfo, !ntfsEnabled.value, chatModel, ntfsEnabled) } ) } @@ -851,7 +851,7 @@ fun CallButton(chat: Chat, contact: Contact, icon: Painter, title: String, media disabledLook = !canCall, onClick = when { - canCall -> { { startChatCall(chat, mediaType) } } + canCall -> { { startChatCall(chat.remoteHostId, chat.chatInfo, mediaType) } } contact.nextSendGrpInv -> { { showCantCallContactSendMessageAlert() } } !contact.active -> { { showCantCallContactDeletedAlert() } } !contact.ready -> { { showCantCallContactConnectingAlert() } } 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 3a6d80fa09..7e754d1e4c 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 @@ -44,72 +44,60 @@ import java.net.URI import kotlin.math.sign @Composable -fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: String) -> Unit) { - val activeChat = remember { mutableStateOf(chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == chatId }) } - val searchText = rememberSaveable { mutableStateOf("") } +// staleChatId means the id that was before chatModel.chatId becomes null. It's needed for Android only to make transition from chat +// to chat list smooth. Otherwise, chat view will become blank right before the transition starts +fun ChatView(staleChatId: State, onComposed: suspend (chatId: String) -> Unit) { + val shouldReturn = remember { mutableStateOf(false) } + val remoteHostId = remember { derivedStateOf { chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.remoteHostId } } val showSearch = rememberSaveable { mutableStateOf(false) } + val activeChatInfo = remember { derivedStateOf { + val info = chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == staleChatId.value }?.chatInfo + if (info == null) { + shouldReturn.value = true + } + return@derivedStateOf info ?: ChatInfo.Direct.sampleData + } + } val user = chatModel.currentUser.value - val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() - val composeState = rememberSaveable(saver = ComposeState.saver()) { - val draft = chatModel.draft.value - val sharedContent = chatModel.sharedContent.value - mutableStateOf( - if (chatModel.draftChatId.value == chatId && draft != null && (sharedContent !is SharedContent.Forward || sharedContent.fromChatInfo.id == chatId)) { - draft - } else { - ComposeState(useLinkPreviews = useLinkPreviews) - } - ) - } - val attachmentOption = rememberSaveable { mutableStateOf(null) } - val attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) - val scope = rememberCoroutineScope() - LaunchedEffect(Unit) { - // snapshotFlow here is because it reacts much faster on changes in chatModel.chatId.value. - // With LaunchedEffect(chatModel.chatId.value) there is a noticeable delay before reconstruction of the view - launch { - snapshotFlow { chatModel.chatId.value } - .distinctUntilChanged() - .filterNotNull() - .collect { chatId -> - if (activeChat.value?.id != chatId) { - // Redisplay the whole hierarchy if the chat is different to make going from groups to direct chat working correctly - // Also for situation when chatId changes after clicking in notification, etc - showSearch.value = false - activeChat.value = chatModel.getChat(chatId) - } - markUnreadChatAsRead(activeChat, chatModel) - } + if (shouldReturn.value || user == null) { + LaunchedEffect(Unit) { + chatModel.chatId.value = null + ModalManager.end.closeModals() } - launch { - snapshotFlow { - /** - * It's possible that in some cases concurrent modification can happen on [ChatModel.chats] list. - * In this case only error log will be printed here (no crash). - * TODO: Re-write [ChatModel.chats] logic to a new list assignment instead of changing content of mutableList to prevent that - * */ - try { - chatModel.chats.value.firstOrNull { chat -> chat.chatInfo.id == chatModel.chatId.value } - } catch (e: ConcurrentModificationException) { - Log.e(TAG, e.stackTraceToString()) - null - } - } - .distinctUntilChanged() - // Only changed chatInfo is important thing. Other properties can be skipped for reducing recompositions - .filter { it != null && it.chatInfo != activeChat.value?.chatInfo } - .collect { - activeChat.value = it - } - } - } - val view = LocalMultiplatformView() - val chat = activeChat.value - if (chat == null || user == null) { - chatModel.chatId.value = null - ModalManager.end.closeModals() } else { - val chatRh = chat.remoteHostId + val chatInfo = activeChatInfo.value + val searchText = rememberSaveable { mutableStateOf("") } + val useLinkPreviews = chatModel.controller.appPrefs.privacyLinkPreviews.get() + val composeState = rememberSaveable(saver = ComposeState.saver()) { + val draft = chatModel.draft.value + val sharedContent = chatModel.sharedContent.value + mutableStateOf( + if (chatModel.draftChatId.value == staleChatId.value && draft != null && (sharedContent !is SharedContent.Forward || sharedContent.fromChatInfo.id == staleChatId.value)) { + draft + } else { + ComposeState(useLinkPreviews = useLinkPreviews) + } + ) + } + val attachmentOption = rememberSaveable { mutableStateOf(null) } + val attachmentBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden) + val scope = rememberCoroutineScope() + LaunchedEffect(Unit) { + // snapshotFlow here is because it reacts much faster on changes in chatModel.chatId.value. + // With LaunchedEffect(chatModel.chatId.value) there is a noticeable delay before reconstruction of the view + launch { + snapshotFlow { chatModel.chatId.value } + .distinctUntilChanged() + .filterNotNull() + .collect { chatId -> + markUnreadChatAsRead(chatId) + showSearch.value = false + } + } + } + val view = LocalMultiplatformView() + + val chatRh = remoteHostId.value // We need to have real unreadCount value for displaying it inside top right button // Having activeChat reloaded on every change in it is inefficient (UI lags) val unreadCount = remember { @@ -118,13 +106,14 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } } val clipboard = LocalClipboardManager.current - when (chat.chatInfo) { + when (chatInfo) { is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> { - val perChatTheme = remember(chat.chatInfo, CurrentColors.value.base) { if (chat.chatInfo is ChatInfo.Direct) chat.chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chat.chatInfo is ChatInfo.Group) chat.chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null } + val perChatTheme = remember(chatInfo, CurrentColors.value.base) { if (chatInfo is ChatInfo.Direct) chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null } val overrides = if (perChatTheme != null) ThemeManager.currentColors(null, perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) else null SimpleXThemeOverride(overrides ?: CurrentColors.collectAsState().value) { ChatLayout( - chat, + remoteHostId = remoteHostId, + chatInfo = activeChatInfo, unreadCount, composeState, composeView = { @@ -133,10 +122,10 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: horizontalAlignment = Alignment.CenterHorizontally ) { if ( - chat.chatInfo is ChatInfo.Direct - && !chat.chatInfo.contact.sndReady - && chat.chatInfo.contact.active - && !chat.chatInfo.contact.nextSendGrpInv + chatInfo is ChatInfo.Direct + && !chatInfo.contact.sndReady + && chatInfo.contact.active + && !chatInfo.contact.nextSendGrpInv ) { Text( generalGetString(MR.strings.contact_connection_pending), @@ -146,7 +135,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: ) } ComposeView( - chatModel, chat, composeState, attachmentOption, + chatModel, Chat(remoteHostId = chatRh, chatInfo = chatInfo, chatItems = emptyList()), composeState, attachmentOption, showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } } ) } @@ -174,35 +163,35 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: var preloadedContactInfo: Pair? = null var preloadedCode: String? = null var preloadedLink: Pair? = null - if (chat.chatInfo is ChatInfo.Direct) { - preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) - preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second - } else if (chat.chatInfo is ChatInfo.Group) { - setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel) - preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) + if (chatInfo is ChatInfo.Direct) { + preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId) + preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second + } else if (chatInfo is ChatInfo.Group) { + setGroupMembers(chatRh, chatInfo.groupInfo, chatModel) + preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chatInfo.groupInfo.groupId) } ModalManager.end.showModalCloseable(true) { close -> - val chat = remember { activeChat }.value - if (chat?.chatInfo is ChatInfo.Direct) { + val chatInfo = remember { activeChatInfo }.value + if (chatInfo is ChatInfo.Direct) { var contactInfo: Pair? by remember { mutableStateOf(preloadedContactInfo) } var code: String? by remember { mutableStateOf(preloadedCode) } - KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) { - contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) + KeyChangeEffect(chatInfo.id, ChatModel.networkStatuses.toMap()) { + contactInfo = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId) preloadedContactInfo = contactInfo - code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second + code = chatModel.controller.apiGetContactCode(chatRh, chatInfo.apiId)?.second preloadedCode = code } - ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close) { + ChatInfoView(chatModel, chatInfo.contact, contactInfo?.first, contactInfo?.second, chatInfo.localAlias, code, close) { showSearch.value = true } - } else if (chat?.chatInfo is ChatInfo.Group) { - var link: Pair? by remember(chat.id) { mutableStateOf(preloadedLink) } - KeyChangeEffect(chat.id) { - setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel) - link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) + } else if (chatInfo is ChatInfo.Group) { + var link: Pair? by remember(chatInfo.id) { mutableStateOf(preloadedLink) } + KeyChangeEffect(chatInfo.id) { + setGroupMembers(chatRh, chatInfo.groupInfo, chatModel) + link = chatModel.controller.apiGetGroupLink(chatRh, chatInfo.groupInfo.groupId) preloadedLink = link } - GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, { + GroupChatInfoView(chatModel, chatRh, chatInfo.id, link?.first, link?.second, { link = it preloadedLink = it }, close, { showSearch.value = true }) @@ -235,7 +224,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } }, loadPrevMessages = { - if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout + if (chatModel.chatId.value != activeChatInfo.value.id) return@ChatLayout val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) val firstId = chatModel.chatItems.value.firstOrNull()?.id if (c != null && firstId != null) { @@ -246,9 +235,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: }, deleteMessage = { itemId, mode -> withBGApi { - val cInfo = chat.chatInfo + val cInfo = chatInfo val toDeleteItem = chatModel.chatItems.value.firstOrNull { it.id == itemId } - val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo) + val toModerate = toDeleteItem?.memberToModerate(chatInfo) val groupInfo = toModerate?.first val groupMember = toModerate?.second val deletedChatItem: ChatItem? @@ -284,7 +273,6 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: }, deleteMessages = { itemIds -> if (itemIds.isNotEmpty()) { - val chatInfo = chat.chatInfo withBGApi { val deleted = chatModel.controller.apiDeleteChatItems( chatRh, chatInfo.chatType, chatInfo.apiId, itemIds, CIDeleteMode.cidmInternal @@ -311,7 +299,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: onComplete.invoke() } }, - startCall = out@{ media -> startChatCall(chat, media) }, + startCall = out@{ media -> startChatCall(chatRh, chatInfo, media) }, endCall = { val call = chatModel.activeCall.value if (call != null) withBGApi { chatModel.callManager.endCall(call) } @@ -344,7 +332,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: }, updateContactStats = { contact -> withBGApi { - val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) + val r = chatModel.controller.apiContactInfo(chatRh, chatInfo.apiId) if (r != null) { val contactStats = r.first if (contactStats != null) @@ -414,8 +402,8 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: suspend fun loadChatItemInfo(): ChatItemInfo? { val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id) if (ciInfo != null) { - if (chat.chatInfo is ChatInfo.Group) { - setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel) + if (chatInfo is ChatInfo.Group) { + setGroupMembers(chatRh, chatInfo.groupInfo, chatModel) } } return ciInfo @@ -455,21 +443,21 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: markRead = { range, unreadCountAfter -> withBGApi { withChats { - markChatItemsRead(chat, range, unreadCountAfter) - ntfManager.cancelNotificationsForChat(chat.id) + markChatItemsRead(chatRh, chatInfo, range, unreadCountAfter) + ntfManager.cancelNotificationsForChat(chatInfo.id) chatModel.controller.apiChatRead( chatRh, - chat.chatInfo.chatType, - chat.chatInfo.apiId, + chatInfo.chatType, + chatInfo.apiId, range ) } } }, - changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) }, + changeNtfsState = { enabled, currentValue -> toggleNotifications(chatRh, chatInfo, enabled, chatModel, currentValue) }, onSearchValueChanged = { value -> if (searchText.value == value) return@ChatLayout - if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout + if (chatModel.chatId.value != activeChatInfo.value.id) return@ChatLayout val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout withBGApi { apiFindMessages(c, chatModel, value) @@ -486,21 +474,21 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: is ChatInfo.ContactConnection -> { val close = { chatModel.chatId.value = null } ModalView(close, showClose = appPlatform.isAndroid, content = { - ContactConnectionInfoView(chatModel, chat.remoteHostId, chat.chatInfo.contactConnection.connReqInv, chat.chatInfo.contactConnection, false, close) + ContactConnectionInfoView(chatModel, chatRh, chatInfo.contactConnection.connReqInv, chatInfo.contactConnection, false, close) }) - LaunchedEffect(chat.id) { - onComposed(chat.id) + LaunchedEffect(chatInfo.id) { + onComposed(chatInfo.id) ModalManager.end.closeModals() chatModel.chatItems.clear() } } is ChatInfo.InvalidJSON -> { val close = { chatModel.chatId.value = null } - ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chat.chatInfo.json) } }, content = { - InvalidJSONView(chat.chatInfo.json) + ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chatInfo.json) } }, content = { + InvalidJSONView(chatInfo.json) }) - LaunchedEffect(chat.id) { - onComposed(chat.id) + LaunchedEffect(chatInfo.id) { + onComposed(chatInfo.id) ModalManager.end.closeModals() chatModel.chatItems.clear() } @@ -510,13 +498,12 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } } -fun startChatCall(chat: Chat, media: CallMediaType) { +fun startChatCall(remoteHostId: Long?, chatInfo: ChatInfo, media: CallMediaType) { withBGApi { - val cInfo = chat.chatInfo - if (cInfo is ChatInfo.Direct) { - val contactInfo = chatModel.controller.apiContactInfo(chat.remoteHostId, cInfo.contact.contactId) + if (chatInfo is ChatInfo.Direct) { + val contactInfo = chatModel.controller.apiContactInfo(remoteHostId, chatInfo.contact.contactId) val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi - chatModel.activeCall.value = Call(remoteHostId = chat.remoteHostId, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile) + chatModel.activeCall.value = Call(remoteHostId = remoteHostId, contact = chatInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile) chatModel.showCallView.value = true chatModel.callCommand.add(WCallCommand.Capabilities(media)) } @@ -525,7 +512,8 @@ fun startChatCall(chat: Chat, media: CallMediaType) { @Composable fun ChatLayout( - chat: Chat, + remoteHostId: State, + chatInfo: State, unreadCount: State, composeState: MutableState, composeView: (@Composable () -> Unit), @@ -574,7 +562,7 @@ fun ChatLayout( Modifier .fillMaxWidth() .desktopOnExternalDrag( - enabled = !attachmentDisabled.value && rememberUpdatedState(chat.userCanSend).value, + enabled = !attachmentDisabled.value && rememberUpdatedState(chatInfo.value).value.userCanSend, onFiles = { paths -> composeState.onFilesAttached(paths.map { it.toURI() }) }, onImage = { // TODO: file is not saved anywhere?! @@ -609,7 +597,7 @@ fun ChatLayout( } Scaffold( - topBar = { ChatInfoToolbar(chat, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) }, + topBar = { ChatInfoToolbar(chatInfo, back, info, startCall, endCall, addMembers, openGroupLink, changeNtfsState, onSearchValueChanged, showSearch) }, bottomBar = composeView, modifier = Modifier.navigationBarsWithImePadding(), floatingActionButton = { floatingButton.value() }, @@ -631,7 +619,7 @@ fun ChatLayout( .padding(contentPadding) ) { ChatItemsList( - chat, unreadCount, composeState, searchValue, + remoteHostId, chatInfo, unreadCount, composeState, searchValue, useLinkPreviews, linkMode, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages, receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem, updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember, @@ -646,7 +634,7 @@ fun ChatLayout( @Composable fun ChatInfoToolbar( - chat: Chat, + chatInfo: State, back: () -> Unit, info: () -> Unit, startCall: (CallMediaType) -> Unit, @@ -671,21 +659,22 @@ fun ChatInfoToolbar( if (appPlatform.isAndroid) { BackHandler(onBack = onBackClicked) } + val chatInfo = chatInfo.value val barButtons = arrayListOf<@Composable RowScope.() -> Unit>() val menuItems = arrayListOf<@Composable () -> Unit>() val activeCall by remember { chatModel.activeCall } - if (chat.chatInfo is ChatInfo.Local) { + if (chatInfo is ChatInfo.Local) { barButtons.add { IconButton( { showMenu.value = false showSearch.value = true - }, enabled = chat.chatInfo.noteFolder.ready + }, enabled = chatInfo.noteFolder.ready ) { Icon( painterResource(MR.images.ic_search), stringResource(MR.strings.search_verb).capitalize(Locale.current), - tint = if (chat.chatInfo.noteFolder.ready) MaterialTheme.colors.primary else MaterialTheme.colors.secondary + tint = if (chatInfo.noteFolder.ready) MaterialTheme.colors.primary else MaterialTheme.colors.secondary ) } } @@ -698,36 +687,36 @@ fun ChatInfoToolbar( } } - if (chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.mergedPreferences.calls.enabled.forUser) { + if (chatInfo is ChatInfo.Direct && chatInfo.contact.mergedPreferences.calls.enabled.forUser) { if (activeCall == null) { barButtons.add { if (appPlatform.isAndroid) { IconButton({ showMenu.value = false startCall(CallMediaType.Audio) - }, enabled = chat.chatInfo.contact.ready && chat.chatInfo.contact.active + }, enabled = chatInfo.contact.ready && chatInfo.contact.active ) { Icon( painterResource(MR.images.ic_call_500), stringResource(MR.strings.icon_descr_audio_call).capitalize(Locale.current), - tint = if (chat.chatInfo.contact.ready && chat.chatInfo.contact.active) MaterialTheme.colors.primary else MaterialTheme.colors.secondary + tint = if (chatInfo.contact.ready && chatInfo.contact.active) MaterialTheme.colors.primary else MaterialTheme.colors.secondary ) } } else { IconButton({ showMenu.value = false startCall(CallMediaType.Video) - }, enabled = chat.chatInfo.contact.ready && chat.chatInfo.contact.active + }, enabled = chatInfo.contact.ready && chatInfo.contact.active ) { Icon( painterResource(MR.images.ic_videocam), stringResource(MR.strings.icon_descr_video_call).capitalize(Locale.current), - tint = if (chat.chatInfo.contact.ready && chat.chatInfo.contact.active) MaterialTheme.colors.primary else MaterialTheme.colors.secondary + tint = if (chatInfo.contact.ready && chatInfo.contact.active) MaterialTheme.colors.primary else MaterialTheme.colors.secondary ) } } } - } else if (activeCall?.contact?.id == chat.id && appPlatform.isDesktop) { + } else if (activeCall?.contact?.id == chatInfo.id && appPlatform.isDesktop) { barButtons.add { val call = remember { chatModel.activeCall }.value val connectedAt = call?.connectedAt @@ -756,7 +745,7 @@ fun ChatInfoToolbar( } } } - if (chat.chatInfo.contact.ready && chat.chatInfo.contact.active && activeCall == null) { + if (chatInfo.contact.ready && chatInfo.contact.active && activeCall == null) { menuItems.add { if (appPlatform.isAndroid) { ItemAction(stringResource(MR.strings.icon_descr_video_call).capitalize(Locale.current), painterResource(MR.images.ic_videocam), onClick = { @@ -771,12 +760,12 @@ fun ChatInfoToolbar( } } } - } else if (chat.chatInfo is ChatInfo.Group && chat.chatInfo.groupInfo.canAddMembers) { - if (!chat.chatInfo.incognito) { + } else if (chatInfo is ChatInfo.Group && chatInfo.groupInfo.canAddMembers) { + if (!chatInfo.incognito) { barButtons.add { IconButton({ showMenu.value = false - addMembers(chat.chatInfo.groupInfo) + addMembers(chatInfo.groupInfo) }) { Icon(painterResource(MR.images.ic_person_add_500), stringResource(MR.strings.icon_descr_add_members), tint = MaterialTheme.colors.primary) } @@ -785,7 +774,7 @@ fun ChatInfoToolbar( barButtons.add { IconButton({ showMenu.value = false - openGroupLink(chat.chatInfo.groupInfo) + openGroupLink(chatInfo.groupInfo) }) { Icon(painterResource(MR.images.ic_add_link), stringResource(MR.strings.group_link), tint = MaterialTheme.colors.primary) } @@ -793,8 +782,8 @@ fun ChatInfoToolbar( } } - if ((chat.chatInfo is ChatInfo.Direct && chat.chatInfo.contact.ready && chat.chatInfo.contact.active) || chat.chatInfo is ChatInfo.Group) { - val ntfsEnabled = remember { mutableStateOf(chat.chatInfo.ntfsEnabled) } + if ((chatInfo is ChatInfo.Direct && chatInfo.contact.ready && chatInfo.contact.active) || chatInfo is ChatInfo.Group) { + val ntfsEnabled = remember { mutableStateOf(chatInfo.ntfsEnabled) } menuItems.add { ItemAction( if (ntfsEnabled.value) stringResource(MR.strings.mute_chat) else stringResource(MR.strings.unmute_chat), @@ -821,8 +810,8 @@ fun ChatInfoToolbar( DefaultTopAppBar( navigationButton = { if (appPlatform.isAndroid || showSearch.value) { NavigationButtonBack(onBackClicked) } }, - title = { ChatInfoToolbarTitle(chat.chatInfo) }, - onTitleClick = if (chat.chatInfo is ChatInfo.Local) null else info, + title = { ChatInfoToolbarTitle(chatInfo) }, + onTitleClick = if (chatInfo is ChatInfo.Local) null else info, showSearch = showSearch.value, onSearchValueChanged = onSearchValueChanged, buttons = barButtons @@ -889,7 +878,8 @@ val CIListStateSaver = run { @Composable fun BoxWithConstraintsScope.ChatItemsList( - chat: Chat, + remoteHostId: State, + chatInfo: State, unreadCount: State, composeState: MutableState, searchValue: State, @@ -922,7 +912,9 @@ fun BoxWithConstraintsScope.ChatItemsList( ) { val listState = rememberLazyListState() val scope = rememberCoroutineScope() - ScrollToBottom(chat.id, listState, chatModel.chatItems) + val remoteHostId = remember { remoteHostId }.value + val chatInfo = remember { chatInfo }.value + ScrollToBottom(chatInfo.id, listState, chatModel.chatItems) var prevSearchEmptiness by rememberSaveable { mutableStateOf(searchValue.value.isEmpty()) } // Scroll to bottom when search value changes from something to nothing and back LaunchedEffect(searchValue.value.isEmpty()) { @@ -947,13 +939,13 @@ fun BoxWithConstraintsScope.ChatItemsList( scope.launch { listState.animateScrollToItem(kotlin.math.min(reversedChatItems.lastIndex, index + 1), -maxHeightRounded) } } } - LaunchedEffect(chat.id) { + LaunchedEffect(chatInfo.id) { var stopListening = false snapshotFlow { listState.layoutInfo.visibleItemsInfo.lastIndex } .distinctUntilChanged() .filter { !stopListening } .collect { - onComposed(chat.id) + onComposed(chatInfo.id) stopListening = true } } @@ -972,7 +964,7 @@ fun BoxWithConstraintsScope.ChatItemsList( val dismissState = rememberDismissState(initialValue = DismissValue.Default) { if (it == DismissValue.DismissedToStart) { scope.launch { - if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chat.chatInfo !is ChatInfo.Local) { + if ((cItem.content is CIContent.SndMsgContent || cItem.content is CIContent.RcvMsgContent) && chatInfo !is ChatInfo.Local) { if (composeState.value.editing) { composeState.value = ComposeState(contextItem = ComposeContextItem.QuotedItem(cItem), useLinkPreviews = useLinkPreviews) } else if (cItem.id != ChatItem.TEMP_LIVE_CHAT_ITEM_ID) { @@ -1006,14 +998,14 @@ fun BoxWithConstraintsScope.ChatItemsList( tryOrShowError("${cItem.id}ChatItem", error = { CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) }) { - ChatItemView(chat.remoteHostId, chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy) + ChatItemView(remoteHostId, chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy) } } @Composable fun ChatItemView(cItem: ChatItem, range: IntRange?, prevItem: ChatItem?) { val voiceWithTransparentBack = cItem.content.msgContent is MsgContent.MCVoice && cItem.content.text.isEmpty() && cItem.quotedItem == null && cItem.meta.itemForwarded == null - if (chat.chatInfo is ChatInfo.Group) { + if (chatInfo is ChatInfo.Group) { if (cItem.chatDir is CIDirection.GroupRcv) { val member = cItem.chatDir.groupMember val (prevMember, memCount) = @@ -1053,7 +1045,7 @@ fun BoxWithConstraintsScope.ChatItemsList( swipeableModifier, horizontalArrangement = Arrangement.spacedBy(4.dp) ) { - Box(Modifier.clickable { showMemberInfo(chat.chatInfo.groupInfo, member) }) { + Box(Modifier.clickable { showMemberInfo(chatInfo.groupInfo, member) }) { MemberImage(member) } ChatItemViewShortHand(cItem, range) @@ -1107,7 +1099,7 @@ fun BoxWithConstraintsScope.ChatItemsList( } } - if (cItem.isRcvNew && chat.id == ChatModel.chatId.value) { + if (cItem.isRcvNew && chatInfo.id == ChatModel.chatId.value) { LaunchedEffect(cItem.id) { scope.launch { delay(600) @@ -1118,7 +1110,7 @@ fun BoxWithConstraintsScope.ChatItemsList( } } } - FloatingButtons(chatModel.chatItems, unreadCount, chat.chatStats.minUnreadItemId, searchValue, markRead, setFloatingButton, listState) + FloatingButtons(chatModel.chatItems, unreadCount, remoteHostId, chatInfo, searchValue, markRead, setFloatingButton, listState) LaunchedEffect(Unit) { snapshotFlow { listState.isScrollInProgress } .collect { @@ -1173,7 +1165,8 @@ private fun ScrollToBottom(chatId: ChatId, listState: LazyListState, chatItems: fun BoxWithConstraintsScope.FloatingButtons( chatItems: State>, unreadCount: State, - minUnreadItemId: Long, + remoteHostId: Long?, + chatInfo: ChatInfo, searchValue: State, markRead: (CC.ItemRange, unreadCountAfter: Int?) -> Unit, setFloatingButton: (@Composable () -> Unit) -> Unit, @@ -1254,6 +1247,7 @@ fun BoxWithConstraintsScope.FloatingButtons( generalGetString(MR.strings.mark_read), painterResource(MR.images.ic_check), onClick = { + val minUnreadItemId = chatModel.chats.value.firstOrNull { it.remoteHostId == remoteHostId && it.id == chatInfo.id }?.chatStats?.minUnreadItemId ?: return@ItemAction markRead( CC.ItemRange(minUnreadItemId, chatItems.value[chatItems.value.size - listState.layoutInfo.visibleItemsInfo.lastIndex - 1].id - 1), bottomUnreadCount @@ -1324,7 +1318,7 @@ private fun TopEndFloatingButton( val interactionSource = interactionSourceWithDetection(onClick, onLongClick) FloatingActionButton( {}, // no action here - modifier.size(48.dp), + modifier.size(48.dp).onRightClick(onLongClick), backgroundColor = MaterialTheme.colors.secondaryVariant, elevation = FloatingActionButtonDefaults.elevation(0.dp, 0.dp), interactionSource = interactionSource, @@ -1408,8 +1402,8 @@ private fun bottomEndFloatingButton( } } -private fun markUnreadChatAsRead(activeChat: MutableState, chatModel: ChatModel) { - val chat = activeChat.value +private fun markUnreadChatAsRead(chatId: String) { + val chat = chatModel.chats.value.firstOrNull { it.id == chatId } if (chat?.chatStats?.unreadChat != true) return withApi { val chatRh = chat.remoteHostId @@ -1419,10 +1413,9 @@ private fun markUnreadChatAsRead(activeChat: MutableState, chatModel: Cha chat.chatInfo.apiId, false ) - if (success && chat.id == activeChat.value?.id) { + if (success) { withChats { - activeChat.value = chat.copy(chatStats = chat.chatStats.copy(unreadChat = false)) - replaceChat(chatRh, chat.id, activeChat.value!!) + replaceChat(chatRh, chat.id, chat.copy(chatStats = chat.chatStats.copy(unreadChat = false))) } } } @@ -1576,12 +1569,8 @@ fun PreviewChatLayout() { val unreadCount = remember { mutableStateOf(chatItems.count { it.isRcvNew }) } val searchValue = remember { mutableStateOf("") } ChatLayout( - chat = Chat( - remoteHostId = null, - chatInfo = ChatInfo.Direct.sampleData, - chatItems = chatItems, - chatStats = Chat.ChatStats() - ), + remoteHostId = remember { mutableStateOf(null) }, + chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) }, unreadCount = unreadCount, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, composeView = {}, @@ -1651,12 +1640,8 @@ fun PreviewGroupChatLayout() { val unreadCount = remember { mutableStateOf(chatItems.count { it.isRcvNew }) } val searchValue = remember { mutableStateOf("") } ChatLayout( - chat = Chat( - remoteHostId = null, - chatInfo = ChatInfo.Group.sampleData, - chatItems = chatItems, - chatStats = Chat.ChatStats() - ), + remoteHostId = remember { mutableStateOf(null) }, + chatInfo = remember { mutableStateOf(ChatInfo.Direct.sampleData) }, unreadCount = unreadCount, composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, composeView = {}, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index 404a8636ef..7429dc2fd4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -836,7 +836,7 @@ fun ComposeView( chatModel.sharedContent.value = null } - val userCanSend = rememberUpdatedState(chat.userCanSend) + val userCanSend = rememberUpdatedState(chat.chatInfo.userCanSend) val sendMsgEnabled = rememberUpdatedState(chat.chatInfo.sendMsgEnabled) val userIsObserver = rememberUpdatedState(chat.userIsObserver) val nextSendGrpInv = rememberUpdatedState(chat.nextSendGrpInv) @@ -936,8 +936,8 @@ fun ComposeView( } } - LaunchedEffect(rememberUpdatedState(chat.userCanSend).value) { - if (!chat.userCanSend) { + LaunchedEffect(rememberUpdatedState(chat.chatInfo.userCanSend).value) { + if (!chat.chatInfo.userCanSend) { clearCurrentDraft() clearState() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index cb05752abe..1f63e61a02 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -57,7 +57,7 @@ fun GroupChatInfoView(chatModel: ChatModel, rhId: Long?, chatId: String, groupLi sendReceipts = sendReceipts, setSendReceipts = { sendRcpts -> val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(sendRcpts = sendRcpts.bool) - updateChatSettings(chat, chatSettings, chatModel) + updateChatSettings(chat.remoteHostId, chat.chatInfo, chatSettings, chatModel) sendReceipts.value = sendRcpts }, members = chatModel.groupMembers @@ -211,7 +211,7 @@ fun MuteButton(chat: Chat, groupInfo: GroupInfo) { disabled = !groupInfo.ready, disabledLook = !groupInfo.ready, onClick = { - toggleNotifications(chat, !ntfsEnabled.value, chatModel, ntfsEnabled) + toggleNotifications(chat.remoteHostId, chat.chatInfo, !ntfsEnabled.value, chatModel, ntfsEnabled) } ) } 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 8fc9d3957c..1f8155d3dd 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 @@ -370,7 +370,7 @@ fun ToggleFavoritesChatAction(chat: Chat, chatModel: ChatModel, favorite: Boolea if (favorite) stringResource(MR.strings.unfavorite_chat) else stringResource(MR.strings.favorite_chat), if (favorite) painterResource(MR.images.ic_star_off) else painterResource(MR.images.ic_star), onClick = { - toggleChatFavorite(chat, !favorite, chatModel) + toggleChatFavorite(chat.remoteHostId, chat.chatInfo, !favorite, chatModel) showMenu.value = false } ) @@ -382,7 +382,7 @@ fun ToggleNotificationsChatAction(chat: Chat, chatModel: ChatModel, ntfsEnabled: if (ntfsEnabled) stringResource(MR.strings.mute_chat) else stringResource(MR.strings.unmute_chat), if (ntfsEnabled) painterResource(MR.images.ic_notifications_off) else painterResource(MR.images.ic_notifications), onClick = { - toggleNotifications(chat, !ntfsEnabled, chatModel) + toggleNotifications(chat.remoteHostId, chat.chatInfo, !ntfsEnabled, chatModel) showMenu.value = false } ) @@ -566,7 +566,7 @@ fun markChatRead(c: Chat, chatModel: ChatModel) { if (chat.chatStats.unreadCount > 0) { val minUnreadItemId = chat.chatStats.minUnreadItemId withChats { - markChatItemsRead(chat) + markChatItemsRead(chat.remoteHostId, chat.chatInfo) } chatModel.controller.apiChatRead( chat.remoteHostId, @@ -824,22 +824,22 @@ fun groupInvitationAcceptedAlert(rhId: Long?) { ) } -fun toggleNotifications(chat: Chat, enableAllNtfs: Boolean, chatModel: ChatModel, currentState: MutableState? = null) { - val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(enableNtfs = if (enableAllNtfs) MsgFilter.All else MsgFilter.None) - updateChatSettings(chat, chatSettings, chatModel, currentState) +fun toggleNotifications(remoteHostId: Long?, chatInfo: ChatInfo, enableAllNtfs: Boolean, chatModel: ChatModel, currentState: MutableState? = null) { + val chatSettings = (chatInfo.chatSettings ?: ChatSettings.defaults).copy(enableNtfs = if (enableAllNtfs) MsgFilter.All else MsgFilter.None) + updateChatSettings(remoteHostId, chatInfo, chatSettings, chatModel, currentState) } -fun toggleChatFavorite(chat: Chat, favorite: Boolean, chatModel: ChatModel) { - val chatSettings = (chat.chatInfo.chatSettings ?: ChatSettings.defaults).copy(favorite = favorite) - updateChatSettings(chat, chatSettings, chatModel) +fun toggleChatFavorite(remoteHostId: Long?, chatInfo: ChatInfo, favorite: Boolean, chatModel: ChatModel) { + val chatSettings = (chatInfo.chatSettings ?: ChatSettings.defaults).copy(favorite = favorite) + updateChatSettings(remoteHostId, chatInfo, chatSettings, chatModel) } -fun updateChatSettings(chat: Chat, chatSettings: ChatSettings, chatModel: ChatModel, currentState: MutableState? = null) { - val newChatInfo = when(chat.chatInfo) { - is ChatInfo.Direct -> with (chat.chatInfo) { +fun updateChatSettings(remoteHostId: Long?, chatInfo: ChatInfo, chatSettings: ChatSettings, chatModel: ChatModel, currentState: MutableState? = null) { + val newChatInfo = when(chatInfo) { + is ChatInfo.Direct -> with (chatInfo) { ChatInfo.Direct(contact.copy(chatSettings = chatSettings)) } - is ChatInfo.Group -> with(chat.chatInfo) { + is ChatInfo.Group -> with(chatInfo) { ChatInfo.Group(groupInfo.copy(chatSettings = chatSettings)) } else -> null @@ -847,19 +847,19 @@ fun updateChatSettings(chat: Chat, chatSettings: ChatSettings, chatModel: ChatMo withBGApi { val res = when (newChatInfo) { is ChatInfo.Direct -> with(newChatInfo) { - chatModel.controller.apiSetSettings(chat.remoteHostId, chatType, apiId, contact.chatSettings) + chatModel.controller.apiSetSettings(remoteHostId, chatType, apiId, contact.chatSettings) } is ChatInfo.Group -> with(newChatInfo) { - chatModel.controller.apiSetSettings(chat.remoteHostId, chatType, apiId, groupInfo.chatSettings) + chatModel.controller.apiSetSettings(remoteHostId, chatType, apiId, groupInfo.chatSettings) } else -> false } if (res && newChatInfo != null) { withChats { - updateChatInfo(chat.remoteHostId, newChatInfo) + updateChatInfo(remoteHostId, newChatInfo) } if (chatSettings.enableNtfs != MsgFilter.All) { - ntfManager.cancelNotificationsForChat(chat.id) + ntfManager.cancelNotificationsForChat(chatInfo.id) } val current = currentState?.value if (current != null) {