[Chat][Feature] Unread messages redux (#558)

This commit is contained in:
ShaunaSong 2022-11-04 09:56:27 -07:00 коммит произвёл GitHub
Родитель 67a137dcf9
Коммит 123a44b47e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 92 добавлений и 46 удалений

Просмотреть файл

@ -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)