mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
android, desktop: enhancements to chat switching (#4567)
This commit is contained in:
committed by
GitHub
parent
5384e2826d
commit
8fbba16f53
@@ -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) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+16
-15
@@ -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
|
||||
|
||||
+3
-3
@@ -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() } }
|
||||
|
||||
+151
-166
@@ -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 = {},
|
||||
|
||||
+3
-3
@@ -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()
|
||||
}
|
||||
|
||||
+2
-2
@@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
+17
-17
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user