[Chat][Feature] Unread messages redux (#558)
This commit is contained in:
Родитель
67a137dcf9
Коммит
123a44b47e
|
@ -43,7 +43,7 @@ internal fun com.azure.android.communication.chat.models.ChatMessage.into(): Mes
|
|||
)
|
||||
}
|
||||
|
||||
internal fun com.azure.android.communication.chat.models.ChatMessageReceivedEvent.into(): MessageInfoModel {
|
||||
internal fun com.azure.android.communication.chat.models.ChatMessageReceivedEvent.into(localParticipantIdentifier: String): MessageInfoModel {
|
||||
return MessageInfoModel(
|
||||
internalId = null,
|
||||
id = this.id,
|
||||
|
@ -55,11 +55,12 @@ internal fun com.azure.android.communication.chat.models.ChatMessageReceivedEven
|
|||
senderDisplayName = this.senderDisplayName,
|
||||
createdOn = this.createdOn,
|
||||
deletedOn = null,
|
||||
editedOn = null
|
||||
editedOn = null,
|
||||
isCurrentUser = localParticipantIdentifier == this.sender.into().id,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun com.azure.android.communication.chat.models.ChatMessageEditedEvent.into(): MessageInfoModel {
|
||||
internal fun com.azure.android.communication.chat.models.ChatMessageEditedEvent.into(localParticipantIdentifier: String): MessageInfoModel {
|
||||
return MessageInfoModel(
|
||||
internalId = null,
|
||||
id = this.id,
|
||||
|
@ -71,11 +72,12 @@ internal fun com.azure.android.communication.chat.models.ChatMessageEditedEvent.
|
|||
senderDisplayName = this.senderDisplayName,
|
||||
createdOn = this.createdOn,
|
||||
deletedOn = null,
|
||||
editedOn = this.editedOn
|
||||
editedOn = this.editedOn,
|
||||
isCurrentUser = localParticipantIdentifier == this.sender.into().id,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun com.azure.android.communication.chat.models.ChatMessageDeletedEvent.into(): MessageInfoModel {
|
||||
internal fun com.azure.android.communication.chat.models.ChatMessageDeletedEvent.into(localParticipantIdentifier: String): MessageInfoModel {
|
||||
return MessageInfoModel(
|
||||
internalId = null,
|
||||
id = this.id,
|
||||
|
@ -87,7 +89,8 @@ internal fun com.azure.android.communication.chat.models.ChatMessageDeletedEvent
|
|||
senderDisplayName = this.senderDisplayName,
|
||||
createdOn = this.createdOn,
|
||||
deletedOn = this.deletedOn,
|
||||
editedOn = null
|
||||
editedOn = null,
|
||||
isCurrentUser = localParticipantIdentifier == this.sender.into().id,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -103,4 +106,5 @@ internal val EMPTY_MESSAGE_INFO_MODEL = MessageInfoModel(
|
|||
senderCommunicationIdentifier = null,
|
||||
deletedOn = null,
|
||||
editedOn = null,
|
||||
isCurrentUser = false
|
||||
)
|
||||
|
|
|
@ -23,7 +23,7 @@ internal data class ChatCompositeDimensions(
|
|||
val typingIndicatorAreaHeight: Dp = 36.dp,
|
||||
val unreadMessagesIndicatorHeight: Dp = 48.dp,
|
||||
val unreadMessagesIndicatorIconHeight: Dp = 18.dp,
|
||||
val unreadMessagesIndicatorIconPadding: PaddingValues = PaddingValues(start = 10.dp, end = 0.dp, top = 2.dp, bottom = 0.dp),
|
||||
val unreadMessagesIndicatorIconPadding: PaddingValues = PaddingValues(start = 10.dp, end = 0.dp, top = 0.dp, bottom = 0.dp),
|
||||
val unreadMessagesIndicatorTextFontSize: TextUnit = 16.sp,
|
||||
val dateHeaderPadding: PaddingValues = PaddingValues(start = 0.dp, end = 0.dp, top = 16.dp, bottom = 0.dp)
|
||||
)
|
||||
|
|
|
@ -30,7 +30,6 @@ internal fun UnreadMessagesIndicatorView(
|
|||
scrollState: LazyListState,
|
||||
visible: Boolean,
|
||||
unreadCount: Int,
|
||||
totalMessages: Int,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val content = LocalContext.current
|
||||
|
@ -47,7 +46,7 @@ internal fun UnreadMessagesIndicatorView(
|
|||
text = {
|
||||
Text(
|
||||
text = when (unreadCount) {
|
||||
1 -> content.getString(R.string.azure_communication_ui_chat_unread_new_messages)
|
||||
1 -> content.getString(R.string.azure_communication_ui_chat_unread_new_message)
|
||||
else -> content.getString(R.string.azure_communication_ui_chat_unread_new_messages, unreadCount.toString())
|
||||
},
|
||||
fontSize = ChatCompositeTheme.dimensions.unreadMessagesIndicatorTextFontSize
|
||||
|
@ -55,7 +54,7 @@ internal fun UnreadMessagesIndicatorView(
|
|||
},
|
||||
onClick = {
|
||||
scope.launch {
|
||||
scrollState.animateScrollToItem(totalMessages)
|
||||
scrollState.animateScrollToItem(0)
|
||||
}
|
||||
},
|
||||
backgroundColor = ChatCompositeTheme.colors.unreadMessageIndicatorBackground,
|
||||
|
@ -74,6 +73,5 @@ internal fun PreviewUnreadMessagesIndicatorView() {
|
|||
rememberLazyListState(),
|
||||
visible = true,
|
||||
unreadCount = 20,
|
||||
totalMessages = 30,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
package com.azure.android.communication.ui.chat.presentation.ui.chat.screens
|
||||
|
||||
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
|
@ -86,26 +86,31 @@ internal fun ChatScreen(
|
|||
FluentCircularIndicator()
|
||||
}
|
||||
} else {
|
||||
MessageListView(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxWidth(),
|
||||
messages = viewModel.messages,
|
||||
scrollState = listState,
|
||||
showLoading = viewModel.areMessagesLoading,
|
||||
dispatchers = viewModel.postAction
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
MessageListView(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxWidth(),
|
||||
messages = viewModel.messages,
|
||||
scrollState = listState,
|
||||
showLoading = viewModel.areMessagesLoading,
|
||||
dispatchers = viewModel.postAction
|
||||
)
|
||||
Box(modifier = Modifier.padding(paddingValues)) {
|
||||
UnreadMessagesIndicatorView(
|
||||
scrollState = listState,
|
||||
visible = viewModel.unreadMessagesIndicatorVisibility,
|
||||
unreadCount = viewModel.unreadMessagesCount,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
bottomBar = {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
UnreadMessagesIndicatorView(
|
||||
scrollState = listState,
|
||||
visible = viewModel.unreadMessagesIndicatorVisibility,
|
||||
unreadCount = viewModel.unreadMessagesCount,
|
||||
totalMessages = viewModel.messages.size/* TODO ViewModelLogic */
|
||||
)
|
||||
|
||||
Box(contentAlignment = Alignment.CenterStart) {
|
||||
TypingIndicatorView(viewModel.typingParticipants.toList())
|
||||
}
|
||||
|
|
|
@ -46,15 +46,12 @@ internal fun buildChatScreenViewModel(
|
|||
dispatch: Dispatch,
|
||||
): ChatScreenViewModel {
|
||||
|
||||
// TODO add logic with last read message
|
||||
var unreadMessagesCount: Int = 0
|
||||
|
||||
return ChatScreenViewModel(
|
||||
messages = messages.toViewModelList(context, localUserIdentifier),
|
||||
areMessagesLoading = !store.getCurrentState().chatState.chatInfoModel.allMessagesFetched,
|
||||
chatStatus = store.getCurrentState().chatState.chatStatus,
|
||||
buildCount = buildCount++,
|
||||
unreadMessagesCount = unreadMessagesCount,
|
||||
unreadMessagesCount = getUnReadMessagesCount(store, messages),
|
||||
error = store.getCurrentState().errorState.chatStateError,
|
||||
postAction = dispatch,
|
||||
typingParticipants = store.getCurrentState().participantState.participantTyping.values.toList(),
|
||||
|
@ -63,3 +60,29 @@ internal fun buildChatScreenViewModel(
|
|||
navigationStatus = store.getCurrentState().navigationState.navigationStatus,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getUnReadMessagesCount(
|
||||
store: AppStore<ReduxState>,
|
||||
messages: List<MessageInfoModel>,
|
||||
): Int {
|
||||
var unreadMessagesCount = 0
|
||||
|
||||
val lastReadId = store.getCurrentState().chatState.lastReadMessageId
|
||||
val lastSendId = store.getCurrentState().chatState.lastSendMessageId
|
||||
var itr = 0
|
||||
while (itr < messages.size) {
|
||||
if (lastReadId.isEmpty()) {
|
||||
break
|
||||
}
|
||||
if (messages[itr].isCurrentUser) {
|
||||
itr++
|
||||
continue
|
||||
}
|
||||
if (messages[itr].id!! > lastReadId && !messages[itr].isCurrentUser && messages[itr].id!! > lastSendId) {
|
||||
unreadMessagesCount++
|
||||
}
|
||||
itr++
|
||||
}
|
||||
|
||||
return unreadMessagesCount
|
||||
}
|
||||
|
|
|
@ -28,8 +28,14 @@ internal class ChatReducerImpl : ChatReducer {
|
|||
is ChatAction.ThreadDeleted -> {
|
||||
state.copy(chatInfoModel = state.chatInfoModel.copy(isThreadDeleted = true))
|
||||
}
|
||||
is ChatAction.MessageSent -> {
|
||||
state.copy(lastSendMessageId = action.messageInfoModel.id ?: "")
|
||||
}
|
||||
is ChatAction.MessageRead -> {
|
||||
state.copy(lastReadMessageId = action.messageId)
|
||||
state.copy(
|
||||
lastReadMessageId = if (state.lastReadMessageId> action.messageId) state.lastReadMessageId
|
||||
else action.messageId
|
||||
)
|
||||
}
|
||||
else -> state
|
||||
}
|
||||
|
|
|
@ -24,7 +24,8 @@ internal class AppReduxState(
|
|||
allMessagesFetched = false,
|
||||
isThreadDeleted = false
|
||||
),
|
||||
lastReadMessageId = ""
|
||||
lastReadMessageId = "",
|
||||
lastSendMessageId = "",
|
||||
)
|
||||
|
||||
override var participantState: ParticipantsState = ParticipantsState(
|
||||
|
|
|
@ -19,4 +19,5 @@ internal data class ChatState(
|
|||
val localParticipantInfoModel: LocalParticipantInfoModel,
|
||||
val chatInfoModel: ChatInfoModel,
|
||||
val lastReadMessageId: String,
|
||||
val lastSendMessageId: String,
|
||||
)
|
||||
|
|
|
@ -109,7 +109,7 @@ internal class ChatEventHandler {
|
|||
val event = chatEvent as ChatMessageReceivedEvent
|
||||
val infoModel = ChatEventModel(
|
||||
eventType = ChatEventType.CHAT_MESSAGE_RECEIVED.into(),
|
||||
infoModel = event.into(),
|
||||
infoModel = event.into(localParticipantIdentifier),
|
||||
eventReceivedOffsetDateTime = event.createdOn
|
||||
)
|
||||
eventSubscriber(infoModel)
|
||||
|
@ -118,7 +118,7 @@ internal class ChatEventHandler {
|
|||
val event = chatEvent as ChatMessageEditedEvent
|
||||
val infoModel = ChatEventModel(
|
||||
eventType = ChatEventType.CHAT_MESSAGE_EDITED.into(),
|
||||
infoModel = event.into(),
|
||||
infoModel = event.into(localParticipantIdentifier),
|
||||
eventReceivedOffsetDateTime = event.editedOn
|
||||
)
|
||||
eventSubscriber(infoModel)
|
||||
|
@ -127,7 +127,7 @@ internal class ChatEventHandler {
|
|||
val event = chatEvent as ChatMessageDeletedEvent
|
||||
val infoModel = ChatEventModel(
|
||||
eventType = ChatEventType.CHAT_MESSAGE_DELETED.into(),
|
||||
infoModel = event.into(),
|
||||
infoModel = event.into(localParticipantIdentifier),
|
||||
eventReceivedOffsetDateTime = event.deletedOn
|
||||
)
|
||||
eventSubscriber(infoModel)
|
||||
|
|
|
@ -22,7 +22,8 @@ internal class ChatReducerUnitTest {
|
|||
val reducer = ChatReducerImpl()
|
||||
val localParticipantInfoModel = mock<LocalParticipantInfoModel> { }
|
||||
val chatInfoModel = mock<ChatInfoModel>()
|
||||
val previousState = ChatState(ChatStatus.NONE, localParticipantInfoModel, chatInfoModel, "")
|
||||
val previousState =
|
||||
ChatState(ChatStatus.NONE, localParticipantInfoModel, chatInfoModel, "", "")
|
||||
val action = ChatAction.Initialization()
|
||||
|
||||
// act
|
||||
|
@ -38,7 +39,8 @@ internal class ChatReducerUnitTest {
|
|||
val reducer = ChatReducerImpl()
|
||||
val localParticipantInfoModel = mock<LocalParticipantInfoModel> { }
|
||||
val chatInfoModel = mock<ChatInfoModel>()
|
||||
val previousState = ChatState(ChatStatus.NONE, localParticipantInfoModel, chatInfoModel, "")
|
||||
val previousState =
|
||||
ChatState(ChatStatus.NONE, localParticipantInfoModel, chatInfoModel, "", "")
|
||||
val action = ChatAction.Initialized()
|
||||
|
||||
// act
|
||||
|
@ -54,10 +56,12 @@ internal class ChatReducerUnitTest {
|
|||
val reducer = ChatReducerImpl()
|
||||
val localParticipantInfoModel = mock<LocalParticipantInfoModel> { }
|
||||
val chatInfoModel = ChatInfoModel(threadId = "", topic = "Previous Chat topic")
|
||||
val previousState = ChatState(ChatStatus.NONE, localParticipantInfoModel, chatInfoModel, "")
|
||||
val previousState =
|
||||
ChatState(ChatStatus.NONE, localParticipantInfoModel, chatInfoModel, "", "")
|
||||
val action = ChatAction.TopicUpdated("New Chat topic")
|
||||
val afterChatInfoModel = ChatInfoModel(threadId = "", topic = "New Chat topic")
|
||||
val afterState = ChatState(ChatStatus.NONE, localParticipantInfoModel, afterChatInfoModel, "")
|
||||
val afterState =
|
||||
ChatState(ChatStatus.NONE, localParticipantInfoModel, afterChatInfoModel, "", "")
|
||||
|
||||
// act
|
||||
val newState = reducer.reduce(previousState, action)
|
||||
|
@ -72,10 +76,12 @@ internal class ChatReducerUnitTest {
|
|||
val reducer = ChatReducerImpl()
|
||||
val localParticipantInfoModel = mock<LocalParticipantInfoModel> { }
|
||||
val chatInfoModel = ChatInfoModel(threadId = "", topic = "", allMessagesFetched = false)
|
||||
val previousState = ChatState(ChatStatus.NONE, localParticipantInfoModel, chatInfoModel, "")
|
||||
val previousState =
|
||||
ChatState(ChatStatus.NONE, localParticipantInfoModel, chatInfoModel, "", "")
|
||||
val action = ChatAction.AllMessagesFetched()
|
||||
val afterChatInfoModel = ChatInfoModel(threadId = "", topic = "", allMessagesFetched = true)
|
||||
val afterState = ChatState(ChatStatus.NONE, localParticipantInfoModel, afterChatInfoModel, "")
|
||||
val afterState =
|
||||
ChatState(ChatStatus.NONE, localParticipantInfoModel, afterChatInfoModel, "", "")
|
||||
|
||||
// act
|
||||
val newState = reducer.reduce(previousState, action)
|
||||
|
@ -96,7 +102,8 @@ internal class ChatReducerUnitTest {
|
|||
allMessagesFetched = false,
|
||||
isThreadDeleted = false
|
||||
)
|
||||
val previousState = ChatState(ChatStatus.NONE, localParticipantInfoModel, chatInfoModel, "")
|
||||
val previousState =
|
||||
ChatState(ChatStatus.NONE, localParticipantInfoModel, chatInfoModel, "", "")
|
||||
|
||||
val action = ChatAction.ThreadDeleted()
|
||||
|
||||
|
@ -106,7 +113,8 @@ internal class ChatReducerUnitTest {
|
|||
allMessagesFetched = false,
|
||||
isThreadDeleted = true
|
||||
)
|
||||
val afterState = ChatState(ChatStatus.NONE, localParticipantInfoModel, afterChatInfoModel, "")
|
||||
val afterState =
|
||||
ChatState(ChatStatus.NONE, localParticipantInfoModel, afterChatInfoModel, "", "")
|
||||
|
||||
// act
|
||||
val newState = reducer.reduce(previousState, action)
|
||||
|
|
Загрузка…
Ссылка в новой задаче