android, desktop: enhancements to chat switching (#4567)

This commit is contained in:
Stanislav Dmitrenko
2024-08-03 00:01:21 +09:00
committed by GitHub
parent 5384e2826d
commit 8fbba16f53
7 changed files with 199 additions and 213 deletions
@@ -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) {}
}
}
@@ -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<ChatItem>,
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
@@ -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() } }
@@ -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<String?>, 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<AttachmentOption?>(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<AttachmentOption?>(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<ConnectionStats?, Profile?>? = null
var preloadedCode: String? = null
var preloadedLink: Pair<String, GroupMemberRole>? = 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<ConnectionStats?, Profile?>? 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<String, GroupMemberRole>? 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<String, GroupMemberRole>? 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<Long?>,
chatInfo: State<ChatInfo>,
unreadCount: State<Int>,
composeState: MutableState<ComposeState>,
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<ChatInfo>,
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<Long?>,
chatInfo: State<ChatInfo>,
unreadCount: State<Int>,
composeState: MutableState<ComposeState>,
searchValue: State<String>,
@@ -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<List<ChatItem>>,
unreadCount: State<Int>,
minUnreadItemId: Long,
remoteHostId: Long?,
chatInfo: ChatInfo,
searchValue: State<String>,
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<Chat?>, 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<Chat?>, 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 = {},
@@ -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()
}
@@ -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)
}
)
}
@@ -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<Boolean>? = 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<Boolean>? = 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<Boolean>? = null) {
val newChatInfo = when(chat.chatInfo) {
is ChatInfo.Direct -> with (chat.chatInfo) {
fun updateChatSettings(remoteHostId: Long?, chatInfo: ChatInfo, chatSettings: ChatSettings, chatModel: ChatModel, currentState: MutableState<Boolean>? = 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) {