Merge branch 'develop' into feature/beta_release_merge

This commit is contained in:
Mohtasim 2022-11-15 11:56:59 -08:00 коммит произвёл GitHub
Родитель fb33be03de c6344e2ac4
Коммит 063b0550d8
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
39 изменённых файлов: 571 добавлений и 171 удалений

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

@ -34,13 +34,11 @@ android {
```groovy
dependencies {
...
implementation 'com.azure.android:azure-communication-ui-calling:<latest stable release version>'
implementation 'com.azure.android:azure-communication-ui-calling:1.1.0'
...
}
```
Please make sure to pick latest stable release version from our [Github Releases](https://github.com/Azure/communication-ui-library-android/releases)
In your project gradle scripts add following lines to `repositories`. For `Android Studio (2020.*)` the `repositories` are in `settings.gradle` `dependencyResolutionManagement(Gradle version 6.8 or greater)`. If you are using old versions of `Android Studio (4.*)` then the `repositories` will be in project level `build.gradle` `allprojects{}`.
```groovy
@ -113,9 +111,9 @@ The snackbar on setup screen with error message may take more time to show up.
## Contributing to the Library
Before developing and contributing to Communication Mobile UI Library, check out our [making a contribution guide](docs/contributing-guide.md).
Included in this repository is a demo of using Mobile UI Library to start a call. You can find the detail of using and developing the UI Library in the [Demo Guide](azure-communication-ui/azure-communication-ui-demo-app).
Included in this repository is a demo of using Mobile UI Library to start a call. You can find the detail of using and developing the UI Library in the [Demo Guide](azure-communication-ui/demo-app).
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. Also, please check our [Contribution Policy](CONTRIBUTING.md).
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. Also, please check our [Contribution Policy](docs/contributing-guide.md).
## Community Help and Support
@ -136,3 +134,5 @@ The chat experience is a work in progress, please be aware that chat and callwit
* [Azure Communication Client and Server Architecture](https://docs.microsoft.com/en-us/azure/communication-services/concepts/client-and-server-architecture)
* [Azure Communication Authentication](https://docs.microsoft.com/en-us/azure/communication-services/concepts/authentication)
* [Azure Communication Service Troubleshooting](https://docs.microsoft.com/en-us/azure/communication-services/concepts/troubleshooting-info)
* [Azure Communication Service UI Calling Library Maven Releases](https://search.maven.org/artifact/com.azure.android/azure-communication-ui-calling)
* [Azure Communication Service Android Calling Hero Sample](https://github.com/Azure-Samples/communication-services-android-calling-hero)

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

@ -7,7 +7,7 @@ public final class CallWithChatCompositeLocalOptions {
private CallWithChatCompositeParticipantViewData participantViewData;
/**
* Create Local Options.
* Get {@link CallWithChatCompositeParticipantViewData}.
*
*/
public CallWithChatCompositeLocalOptions() {

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

@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.callwithchat.models;
/**
* Provides navigation bar view data to Call Composite including title and subtitle.
*
* Create an instance of {@link CallWithChatCompositeNavigationBarViewData} and pass it to
* {@link CallWithChatCompositeLocalOptions} when launching a new call.
*
*/
public final class CallWithChatCompositeNavigationBarViewData {
private String callTitle = null;
private String callSubtitle = null;
/**
* Get the call title.
* @return The title of the call.
*/
public String getCallTitle() {
return callTitle;
}
/**
* Set the call title of the call setup screen to the supplied String.
* @param callTitle Title of the call.
* @return The current {@link CallWithChatCompositeNavigationBarViewData}.
*/
public CallWithChatCompositeNavigationBarViewData setCallTitle(final String callTitle) {
this.callTitle = callTitle;
return this;
}
/**
* Get the call sub title.
* @return The subtitle of the call.
*/
public String getCallSubtitle() {
return callSubtitle;
}
/**
* Set the subtitle of the call setup screen to the supplied String.
* @param callSubtitle Subtitle of the call.
* @return The current {@link CallWithChatCompositeNavigationBarViewData}.
*/
public CallWithChatCompositeNavigationBarViewData setCallSubtitle(final String callSubtitle) {
this.callSubtitle = callSubtitle;
return this;
}
}

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

@ -1515,7 +1515,6 @@ internal class CallingMiddlewareActionHandlerUnitTest : ACSBaseTestCoroutine() {
MutableSharedFlow<MutableMap<String, ParticipantInfoModel>>()
val callInfoModelStateFlow =
MutableStateFlow(CallInfoModel(CallingStatus.LOCAL_HOLD, null))
val callIdFlow = MutableStateFlow<String?>(null)
val isMutedSharedFlow = MutableSharedFlow<Boolean>()
val isRecordingSharedFlow = MutableSharedFlow<Boolean>()
@ -1526,6 +1525,7 @@ internal class CallingMiddlewareActionHandlerUnitTest : ACSBaseTestCoroutine() {
val mockCallingService: CallingService = mock {
on { getParticipantsInfoModelSharedFlow() } doReturn callingServiceParticipantsSharedFlow
on { startCall(any(), any()) } doReturn CompletableFuture<Void>()
on { getCallIdStateFlow() } doReturn callIdFlow
on { getCallInfoModelEventSharedFlow() } doReturn callInfoModelStateFlow
on { getCallIdStateFlow() } doReturn callIdFlow
on { getCallInfoModelEventSharedFlow() } doReturn callInfoModelStateFlow

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

@ -91,6 +91,9 @@ dependencies {
debugImplementation "androidx.customview:customview:1.2.0-alpha01"
debugImplementation "androidx.customview:customview-poolingcontainer:1.0.0-alpha01"
api ("com.azure.android:azure-communication-chat:$azure_chat_sdk_version")
api ("com.azure.android:azure-communication-common:$azure_common_sdk_version")
testImplementation "junit:junit:$junit_version"
testImplementation "org.mockito:mockito-inline:$mockito_inline_version"
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockito_kotlin_version"

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

@ -14,6 +14,7 @@
android:exported="false"
android:label="@string/azure_communication_ui_chat_title_activity_compose_chat"
android:theme="@style/AzureCommunicationUIChat.Theme"
android:windowSoftInputMode="adjustResize"
/>
</application>

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

@ -20,6 +20,7 @@ import com.azure.android.communication.ui.chat.presentation.ChatCompositeActivit
import com.azure.android.communication.ui.chat.presentation.ui.container.ChatView;
/**
* Azure android communication chat composite component.
*

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

@ -54,7 +54,6 @@ internal class ChatContainer(
context: Context,
remoteOptions: ChatCompositeRemoteOptions,
localOptions: ChatCompositeLocalOptions?,
) {
// currently only single instance is supported
if (!started) {

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

@ -13,8 +13,8 @@ import org.threeten.bp.OffsetDateTime
internal data class MessageInfoModel(
val id: String?,
val internalId: String? = null,
val messageType: ChatMessageType?,
val content: String?,
val messageType: ChatMessageType? = null,
val content: String? = null,
val topic: String? = null,
val participants: List<String> = emptyList(),
val version: String? = null,
@ -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,7 @@ internal val EMPTY_MESSAGE_INFO_MODEL = MessageInfoModel(
senderCommunicationIdentifier = null,
deletedOn = null,
editedOn = null,
isCurrentUser = false
)
internal const val INVALID_INDEX = -1

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

@ -15,17 +15,22 @@ import androidx.compose.ui.unit.sp
@Immutable
internal data class ChatCompositeDimensions(
@Dimension
val messageBubbleLeftSpacing: Dp = 48.dp,
val messageAvatarSize: Dp = 24.dp,
// Left Rail where Avatar is
val messageAvatarRailWidth: Dp = 32.dp,
val messageReceiptRailWidth: Dp = 20.dp,
val messageUsernamePaddingEnd: Dp = 8.dp,
val messagePadding: PaddingValues = PaddingValues(start = 10.dp, end = 10.dp, top = 8.dp, bottom = 8.dp),
val systemMessagePadding: PaddingValues = PaddingValues(start = 20.dp, end = 5.dp, top = 10.dp, bottom = 10.dp),
val messageOuterPadding: PaddingValues = PaddingValues(start = 0.dp, end = 0.dp, top = 1.dp, bottom = 1.dp),
val messageInnerPadding: PaddingValues = PaddingValues(start = 8.dp, end = 8.dp, top = 8.dp, bottom = 8.dp),
val systemMessagePadding: PaddingValues = PaddingValues(start = 20.dp, end = 5.dp, top = 8.dp, bottom = 8.dp),
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 unreadMessagesIndicatorPadding: PaddingValues = PaddingValues(start = 0.dp, end = 0.dp, top = 0.dp, bottom = 4.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)
val dateHeaderPadding: PaddingValues = PaddingValues(start = 0.dp, end = 0.dp, top = 12.dp, bottom = 4.dp),
val messageAvatarPadding: PaddingValues = PaddingValues(start = 0.dp, end = 4.dp, top = 0.dp, bottom = 0.dp),
val messageRead: PaddingValues = PaddingValues(start = 3.dp, end = 4.99.dp, top = 3.dp, bottom = 3.dp)
)
internal val LocalChatCompositeDimensions = staticCompositionLocalOf {

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

@ -5,6 +5,7 @@ package com.azure.android.communication.ui.chat.presentation.ui.chat.components
import androidx.annotation.DrawableRes
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.tooling.preview.Preview
@ -19,17 +20,21 @@ internal fun AvatarView(
avatarSize: AvatarSize = AvatarSize.LARGE,
@DrawableRes image: Int = -1,
isGrouped: Boolean = false,
modifier: Modifier = Modifier
) {
AndroidView(factory = {
val view = AvatarView(it)
view.name = name ?: ""
view.avatarSize = avatarSize
color?.apply {
view.avatarBackgroundColor = toArgb()
}
AndroidView(
modifier = modifier,
factory = {
val view = AvatarView(it)
view.name = name ?: ""
view.avatarSize = avatarSize
color?.apply {
view.avatarBackgroundColor = toArgb()
}
view
})
view
}
)
}
@Preview

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

@ -45,7 +45,8 @@ internal fun BottomBarView(
SendMessageButtonView(
contentDescription = stringResource(R.string.azure_communication_ui_chat_message_send_button_content_description, messageInputTextState.value),
chatStatus = chatStatus
chatStatus = chatStatus,
clickable = messageInputTextState.value.isNotBlank()
) {
sendButtonOnclick(postAction, messageInputTextState)
}

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

@ -18,7 +18,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.TextStyle
@ -26,7 +25,6 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.azure.android.communication.ui.chat.R
import com.azure.android.communication.ui.chat.presentation.style.ChatCompositeTheme
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -35,6 +33,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import com.azure.android.communication.ui.chat.R
import com.azure.android.communication.ui.chat.presentation.ui.chat.UITestTags
import com.azure.android.communication.ui.chat.redux.action.Action
import com.azure.android.communication.ui.chat.redux.action.ChatAction

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

@ -10,7 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
@ -44,13 +44,15 @@ internal fun MessageListView(
requestPages(scrollState, messages, dispatchers)
if (messages.isNotEmpty()) {
sendReadReceipt(scrollState, messages, dispatchers)
autoScrollToBottom(scrollState, messages)
}
LazyColumn(
modifier = modifier.fillMaxHeight(),
state = scrollState,
reverseLayout = true,
) {
items(messages.asReversed()) { message ->
itemsIndexed(messages.asReversed(), key = { index, item -> item.message.id ?: index }) { index, message ->
MessageView(message)
}
if (messages.isNotEmpty() && showLoading) {
@ -91,6 +93,21 @@ private fun requestPages(
}
}
@Composable
private fun autoScrollToBottom(
scrollState: LazyListState,
messages: List<MessageViewModel>
) {
val wasAtEnd = remember { mutableStateOf(scrollState.firstVisibleItemIndex) }
val isAtEnd = scrollState.firstVisibleItemIndex
if (wasAtEnd.value == 0 && wasAtEnd.value != isAtEnd) {
LaunchedEffect(messages.last()) {
scrollState.scrollToItem(0)
}
}
wasAtEnd.value = isAtEnd
}
@Composable
private fun sendReadReceipt(
scrollState: LazyListState,

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

@ -10,7 +10,6 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.width
@ -33,15 +32,20 @@ import com.azure.android.communication.ui.chat.preview.MOCK_LOCAL_USER_ID
import com.azure.android.communication.ui.chat.preview.MOCK_MESSAGES
import com.azure.android.communication.ui.chat.service.sdk.wrapper.ChatMessageType
import com.jakewharton.threetenabp.AndroidThreeTen
import com.microsoft.fluentui.persona.AvatarSize
import org.threeten.bp.OffsetDateTime
import org.threeten.bp.format.DateTimeFormatter
val timeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("h:m a")
val timeFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("h:mm a")
@Composable
internal fun MessageView(viewModel: MessageViewModel) {
Column {
Column(
modifier = Modifier.padding(ChatCompositeTheme.dimensions.messageOuterPadding),
) {
// Date Header Part
if (viewModel.dateHeaderText != null) {
Box(
contentAlignment = Alignment.Center,
@ -64,6 +68,7 @@ internal fun MessageView(viewModel: MessageViewModel) {
icon = R.drawable.azure_communication_ui_chat_ic_topic_changed_filled, /* TODO: update icon */
stringResource = R.string.azure_communication_ui_chat_topic_updated,
substitution = listOf(viewModel.message.topic ?: "Unknown")
)
ChatMessageType.PARTICIPANT_ADDED -> SystemMessage(
icon = R.drawable.azure_communication_ui_chat_ic_participant_added_filled,
@ -88,13 +93,13 @@ internal fun MessageView(viewModel: MessageViewModel) {
private fun SystemMessage(icon: Int, stringResource: Int, substitution: List<String>) {
val text = LocalContext.current.getString(stringResource, substitution.joinToString(", "))
Row(verticalAlignment = Alignment.CenterVertically) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(ChatCompositeTheme.dimensions.systemMessagePadding)
) {
Icon(
painter = painterResource(id = icon),
contentDescription = "Participant Added",
modifier = Modifier.padding(
ChatCompositeTheme.dimensions.systemMessagePadding
),
contentDescription = text,
tint = ChatCompositeTheme.colors.systemIconColor
)
BasicText(text = text, style = ChatCompositeTheme.typography.systemMessage)
@ -103,58 +108,90 @@ private fun SystemMessage(icon: Int, stringResource: Int, substitution: List<Str
@Composable
private fun BasicChatMessage(viewModel: MessageViewModel) {
Row(
Modifier.padding(2.dp),
) {
if (viewModel.isLocalUser) {
Box(modifier = Modifier.weight(1.0f))
}
Box(modifier = Modifier.size(ChatCompositeTheme.dimensions.messageBubbleLeftSpacing)) {
if (viewModel.showUsername) {
AvatarView(name = viewModel.message.senderDisplayName)
Box(modifier = Modifier.fillMaxWidth()) {
Row(modifier = Modifier.align(alignment = if (viewModel.isLocalUser) Alignment.TopEnd else Alignment.TopStart)) {
// Avatar Rail (Left Padding)
Box(modifier = Modifier.width(ChatCompositeTheme.dimensions.messageAvatarRailWidth)) {
// Display the Avatar
if (viewModel.showUsername) {
AvatarView(
name = viewModel.message.senderDisplayName,
avatarSize = AvatarSize.SMALL,
modifier = Modifier
.align(alignment = Alignment.TopEnd)
.padding(ChatCompositeTheme.dimensions.messageAvatarPadding)
)
}
}
Box(modifier = Modifier.weight(1.0f)) {
Box(
Modifier.background(
color = when (viewModel.isLocalUser) {
true -> ChatCompositeTheme.colors.messageBackgroundSelf
false -> ChatCompositeTheme.colors.messageBackground
},
shape = ChatCompositeTheme.shapes.messageBubble,
).align(alignment = if (viewModel.isLocalUser) Alignment.TopEnd else Alignment.TopStart)
) {
messageContent(viewModel)
}
}
Box(
modifier = Modifier
.width(ChatCompositeTheme.dimensions.messageReceiptRailWidth)
.align(alignment = Alignment.Bottom)
) {
// Display the Read Receipt
androidx.compose.animation.AnimatedVisibility(visible = viewModel.isRead) {
Icon(
painter =
painterResource(
id =
R.drawable.azure_communication_ui_chat_ic_fluent_message_read_10_filled
),
contentDescription = "Message Read",
tint = ChatCompositeTheme.colors.unreadMessageIndicatorBackground,
modifier = Modifier.padding(start = 4.dp)
)
}
}
}
Box(
Modifier.background(
color = when (viewModel.isLocalUser) {
true -> ChatCompositeTheme.colors.messageBackgroundSelf
false -> ChatCompositeTheme.colors.messageBackground
},
}
}
shape = ChatCompositeTheme.shapes.messageBubble
)
) {
Box(
modifier = Modifier.padding(ChatCompositeTheme.dimensions.messagePadding)
) {
Column {
if (viewModel.showUsername || viewModel.showTime) {
Row {
if (viewModel.showUsername) {
BasicText(
viewModel.message.senderDisplayName ?: "Unknown Sender",
style = ChatCompositeTheme.typography.messageHeader,
modifier = Modifier.padding(PaddingValues(end = ChatCompositeTheme.dimensions.messageUsernamePaddingEnd))
)
}
if (viewModel.showTime) {
BasicText(
viewModel.message.createdOn?.format(timeFormat)
?: "Unknown Time",
style = ChatCompositeTheme.typography.messageHeaderDate,
)
}
}
}
if (viewModel.message.messageType == ChatMessageType.HTML) {
HtmlText(html = viewModel.message.content ?: "Empty")
} else {
@Composable
private fun messageContent(viewModel: MessageViewModel) {
Box(
modifier = Modifier.padding(ChatCompositeTheme.dimensions.messageInnerPadding)
) {
Column {
if (viewModel.showUsername || viewModel.showTime) {
Row {
if (viewModel.showUsername) {
BasicText(
text = viewModel.message.content ?: "Empty"
viewModel.message.senderDisplayName ?: "Unknown Sender",
style = ChatCompositeTheme.typography.messageHeader,
modifier = Modifier.padding(PaddingValues(end = ChatCompositeTheme.dimensions.messageUsernamePaddingEnd))
)
}
if (viewModel.showTime) {
BasicText(
viewModel.message.createdOn?.format(timeFormat)
?: "Unknown Time",
style = ChatCompositeTheme.typography.messageHeaderDate,
)
}
}
}
if (viewModel.message.messageType == ChatMessageType.HTML) {
HtmlText(html = viewModel.message.content ?: "Empty")
} else {
BasicText(
text = viewModel.message.content ?: "Empty"
)
}
}
}
}
@ -181,7 +218,11 @@ internal fun PreviewChatCompositeMessage() {
.width(500.dp)
.background(color = ChatCompositeTheme.colors.background)
) {
val vms = MOCK_MESSAGES.toViewModelList(LocalContext.current, MOCK_LOCAL_USER_ID)
val vms = MOCK_MESSAGES.toViewModelList(
LocalContext.current,
MOCK_LOCAL_USER_ID,
OffsetDateTime.now()
)
for (a in 0 until vms.size) {
MessageView(vms[a])
}

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

@ -27,19 +27,20 @@ internal fun SendMessageButtonView(
contentDescription: String,
modifier: Modifier = Modifier,
chatStatus: ChatStatus,
clickable: Boolean = false,
onClick: () -> Unit = {},
) {
val semantics = Modifier.semantics {
this.contentDescription = contentDescription
this.role = Role.Image
}
val painter = if (chatStatus == ChatStatus.INITIALIZED)
val painter = if (chatStatus == ChatStatus.INITIALIZED && clickable)
painterResource(id = R.drawable.azure_communication_ui_chat_ic_fluent_send_message_button_20_filled_enabled)
else
painterResource(id = R.drawable.azure_communication_ui_chat_ic_fluent_send_message_button_20_filled_disabled)
Box(
modifier = Modifier.testTag(UITestTags.MESSAGE_SEND_BUTTON).clickable {
if (chatStatus == ChatStatus.INITIALIZED) {
if (chatStatus == ChatStatus.INITIALIZED && clickable) {
onClick()
}
}

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

@ -30,7 +30,6 @@ internal fun UnreadMessagesIndicatorView(
scrollState: LazyListState,
visible: Boolean,
unreadCount: Int,
totalMessages: Int,
) {
val scope = rememberCoroutineScope()
val content = LocalContext.current
@ -39,7 +38,8 @@ internal fun UnreadMessagesIndicatorView(
icon = {
Icon(
painterResource(id = R.drawable.azure_communication_ui_chat_ic_fluent_arrow_down_16_filled),
modifier = Modifier.height(ChatCompositeTheme.dimensions.unreadMessagesIndicatorIconHeight)
modifier = Modifier
.height(ChatCompositeTheme.dimensions.unreadMessagesIndicatorIconHeight)
.padding(ChatCompositeTheme.dimensions.unreadMessagesIndicatorIconPadding),
contentDescription = null
)
@ -47,15 +47,20 @@ internal fun UnreadMessagesIndicatorView(
text = {
Text(
text = when (unreadCount) {
1 -> content.getString(R.string.azure_communication_ui_chat_unread_new_messages)
else -> content.getString(R.string.azure_communication_ui_chat_unread_new_messages, unreadCount.toString())
in Int.MIN_VALUE..0 -> return@ExtendedFloatingActionButton
1 -> content.getString(R.string.azure_communication_ui_chat_unread_new_message)
in 2..99 -> content.getString(
R.string.azure_communication_ui_chat_unread_new_messages,
unreadCount.toString()
)
else -> content.getString(R.string.azure_communication_ui_chat_many_unread_new_messages)
},
fontSize = ChatCompositeTheme.dimensions.unreadMessagesIndicatorTextFontSize
)
},
onClick = {
scope.launch {
scrollState.animateScrollToItem(totalMessages)
scrollState.animateScrollToItem(0)
}
},
backgroundColor = ChatCompositeTheme.colors.unreadMessageIndicatorBackground,
@ -74,6 +79,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,36 @@ 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)
.padding(ChatCompositeTheme.dimensions.unreadMessagesIndicatorPadding)
) {
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())
}

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

@ -13,6 +13,7 @@ import com.azure.android.communication.ui.chat.redux.action.Action
import com.azure.android.communication.ui.chat.redux.state.ChatStatus
import com.azure.android.communication.ui.chat.redux.state.NavigationStatus
import com.azure.android.communication.ui.chat.redux.state.ReduxState
import kotlin.math.max
// View Model for the Chat Screen
internal data class ChatScreenViewModel(
@ -46,15 +47,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),
messages = messages.toViewModelList(context, localUserIdentifier, store.getCurrentState().participantState.latestReadMessageTimestamp),
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 +61,18 @@ internal fun buildChatScreenViewModel(
navigationStatus = store.getCurrentState().navigationState.navigationStatus,
)
}
private fun getUnReadMessagesCount(
store: AppStore<ReduxState>,
messages: List<MessageInfoModel>,
): Int {
val lastReadId = store.getCurrentState().chatState.lastReadMessageId
val lastSendId = store.getCurrentState().chatState.lastSendMessageId
val internalLastReadIndex = messages.indexOf(MessageInfoModel(id = lastReadId))
val internalLastSendIndex = messages.indexOf(MessageInfoModel(id = lastSendId))
val internalLastIndex = max(internalLastReadIndex, internalLastSendIndex)
return if (internalLastIndex == -1) 0 else messages.size - internalLastIndex - 1
}

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

@ -21,15 +21,17 @@ internal class MessageViewModel(
val showTime: Boolean,
val dateHeaderText: String?,
val isLocalUser: Boolean,
val isRead: Boolean
)
internal fun List<MessageInfoModel>.toViewModelList(context: Context, localUserIdentifier: String) =
InfoModelToViewModelAdapter(context, this, localUserIdentifier) as List<MessageViewModel>
internal fun List<MessageInfoModel>.toViewModelList(context: Context, localUserIdentifier: String, latestReadMessageTimestamp: OffsetDateTime = OffsetDateTime.MIN) =
InfoModelToViewModelAdapter(context, this, localUserIdentifier, latestReadMessageTimestamp) as List<MessageViewModel>
private class InfoModelToViewModelAdapter(
private val context: Context,
private val messages: List<MessageInfoModel>,
private val localUserIdentifier: String
private val localUserIdentifier: String,
private val latestReadMessageTimestamp: OffsetDateTime
) :
List<MessageViewModel> {
@ -37,8 +39,10 @@ private class InfoModelToViewModelAdapter(
// Generate Message View Model here
val lastMessage = try { messages[index - 1] } catch (e: IndexOutOfBoundsException) { EMPTY_MESSAGE_INFO_MODEL }
// val lastLocalUserMessage =
val thisMessage = messages[index]
val isLocalUser = thisMessage.senderCommunicationIdentifier?.id == localUserIdentifier || thisMessage.isCurrentUser
val currentMessageTime = thisMessage.editedOn ?: thisMessage.createdOn
return MessageViewModel(
messages[index],
@ -55,7 +59,8 @@ private class InfoModelToViewModelAdapter(
thisMessage.createdOn ?: OffsetDateTime.now()
),
isLocalUser = isLocalUser
isLocalUser = isLocalUser,
isRead = isLocalUser && (currentMessageTime != null && currentMessageTime <= latestReadMessageTimestamp)
)
}

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

@ -45,11 +45,31 @@ internal val MOCK_MESSAGES get(): List<MessageInfoModel> {
senderDisplayName = userB_Display,
content = "Hi Peter, thanks for following up with me",
messageType = ChatMessageType.TEXT,
id = null,
id = OffsetDateTime.now().toString(),
internalId = null,
createdOn = OffsetDateTime.now().minusDays(1).minusMinutes(12)
),
MessageInfoModel(
senderCommunicationIdentifier = userB_ID,
senderDisplayName = userB_Display,
content = "I like to type",
messageType = ChatMessageType.TEXT,
id = null,
internalId = null,
createdOn = OffsetDateTime.now().minusDays(1).minusMinutes(11)
),
MessageInfoModel(
senderCommunicationIdentifier = userB_ID,
senderDisplayName = userB_Display,
content = "a lot",
messageType = ChatMessageType.TEXT,
id = null,
internalId = null,
createdOn = OffsetDateTime.now().minusDays(1).minusMinutes(10)
),
MessageInfoModel(
content = null,
messageType = ChatMessageType.PARTICIPANT_ADDED,

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

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

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

@ -23,6 +23,7 @@ internal class MessageRepository private constructor(
override fun addServerMessage(message: MessageInfoModel) = writerDelegate.addServerMessage(message = message)
override fun removeMessage(message: MessageInfoModel) = writerDelegate.removeMessage(message = message)
override fun editMessage(message: MessageInfoModel) = writerDelegate.editMessage(message = message)
override fun indexOf(element: MessageInfoModel) = readerDelegate.indexOf(element)
// TODO: We should be using read interface to get last message in list
// This isn't a write message

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

@ -18,6 +18,7 @@ internal abstract class MessageRepositoryReader : List<MessageInfoModel> {
override fun isEmpty(): Boolean {
return size == 0
}
final override fun contains(element: MessageInfoModel): Boolean {
throw RuntimeException("Not implemented on the Message Repository")
}
@ -26,10 +27,6 @@ internal abstract class MessageRepositoryReader : List<MessageInfoModel> {
throw RuntimeException("Not implemented on the Message Repository")
}
final override fun indexOf(element: MessageInfoModel): Int {
throw RuntimeException("Not implemented on the Message Repository")
}
final override fun iterator(): Iterator<MessageInfoModel> {
throw RuntimeException("Not implemented on the Message Repository")
}

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

@ -80,4 +80,16 @@ internal class MessageRepositoryListReader(private val writer: MessageRepository
}
override val size: Int get() = writer.messages.size
override fun indexOf(element: MessageInfoModel): Int {
val messageId = element.id!!.toLong()
var index = 0
for (message in writer.messages) {
if (messageId == message.id!!.toLong()) {
break
}
index++
}
return index
}
}

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

@ -4,6 +4,7 @@
package com.azure.android.communication.ui.chat.repository.storage
import com.azure.android.communication.ui.chat.models.EMPTY_MESSAGE_INFO_MODEL
import com.azure.android.communication.ui.chat.models.INVALID_INDEX
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.repository.MessageRepositoryReader
import com.azure.android.communication.ui.chat.repository.MessageRepositoryWriter
@ -82,6 +83,25 @@ internal class MessageRepositorySkipListWriter : MessageRepositoryWriter {
return skipListStorage.get(key)!!
}
fun searchIndexByID(messageId: Long): Int {
var highestKey = skipListStorage.lastKey()
var lowestKey = skipListStorage.firstKey()
var midKey: Long = 0
while (lowestKey <= highestKey) {
midKey = (lowestKey + highestKey).div(2)
if (messageId < midKey) {
highestKey = midKey - 1
} else if (messageId > midKey) {
lowestKey = midKey + 1
} else {
break
}
}
return skipListStorage.headMap(midKey).size
}
private fun getOrderId(message: MessageInfoModel): Long {
return message.id?.toLong() ?: 0L
}
@ -117,4 +137,10 @@ internal class MessageRepositorySkipListReader(private val writer: MessageReposi
} catch (exception: Exception) {
EMPTY_MESSAGE_INFO_MODEL
}
override fun indexOf(element: MessageInfoModel): Int = try {
writer.searchIndexByID(element.id!!.toLong())
} catch (exception: Exception) {
INVALID_INDEX
}
}

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

@ -4,6 +4,7 @@
package com.azure.android.communication.ui.chat.repository.storage
import com.azure.android.communication.ui.chat.models.EMPTY_MESSAGE_INFO_MODEL
import com.azure.android.communication.ui.chat.models.INVALID_INDEX
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.repository.MessageRepositoryReader
import com.azure.android.communication.ui.chat.repository.MessageRepositoryWriter
@ -82,6 +83,25 @@ internal class MessageRepositoryTreeWriter : MessageRepositoryWriter {
return treeMapStorage.get(key)!!
}
fun searchIndexByID(messageId: Long): Int {
var highestKey = treeMapStorage.lastKey()
var lowestKey = treeMapStorage.firstKey()
var midKey: Long = 0
while (lowestKey <= highestKey) {
midKey = (lowestKey + highestKey).div(2)
if (messageId < midKey) {
highestKey = midKey - 1
} else if (messageId > midKey) {
lowestKey = midKey + 1
} else {
break
}
}
return treeMapStorage.headMap(midKey).size
}
private fun getOrderId(message: MessageInfoModel): Long {
return message.id?.toLong() ?: 0L
}
@ -117,4 +137,10 @@ internal class MessageRepositoryTreeReader(private val writer: MessageRepository
} catch (exception: Exception) {
EMPTY_MESSAGE_INFO_MODEL
}
override fun indexOf(element: MessageInfoModel): Int = try {
writer.searchIndexByID(element.id!!.toLong())
} catch (exception: Exception) {
INVALID_INDEX
}
}

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

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

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

@ -6,11 +6,12 @@
<string name="azure_communication_ui_chat_chat_action_bar_title">Chat</string>
<string name="azure_communication_ui_chat_title_activity_compose_chat">ComposeChatActivity</string>
<string name="azure_communication_ui_chat_demo_app_title">Chat Composite Demo App</string>
<string name="azure_communication_ui_chat_enter_a_message">Hey, whats up?</string>
<string name="azure_communication_ui_chat_enter_a_message">Type a new message</string>
<string name="azure_communication_ui_chat_other">other</string>
<string name="azure_communication_ui_chat_others">others</string>
<string name="azure_communication_ui_chat_unread_new_message">1 new message</string>
<string name="azure_communication_ui_chat_unread_new_messages">%1$s new messages</string>
<string name="azure_communication_ui_chat_many_unread_new_messages">99+ new messages</string>
<string name="azure_communication_ui_chat_first_name_is_typing">%1$s is typing</string>
<string name="azure_communication_ui_chat_two_names_are_typing">%1$s and %2$s are typing"</string>
<string name="azure_communication_ui_chat_three_or_more_are_typing">%1$s, %2$s and %3$d %4$s are typing</string>

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

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

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

@ -279,7 +279,6 @@ class ParticipantsReducerUnitTest {
// arrange
val reducer = ParticipantsReducerImpl()
val previousState = ParticipantsState(
participants = listOf(
userOne,
userTwo,

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

@ -162,4 +162,31 @@ internal class MessageRepositoryListStorageUnitTest {
Assert.assertEquals("6", repository[6].id)
Assert.assertEquals("7", repository[7].id)
}
@Test
fun messageRepositoryListStorage_indexOfTest() {
val storage = MessageRepository.createListBackedRepository()
val numberOfTestMessages = 50
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
Assert.assertEquals(
1,
storage.indexOf(
MessageInfoModel(
id = "2",
content = "",
messageType = ChatMessageType.TEXT
)
)
)
}
}

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

@ -169,4 +169,31 @@ internal class MessageRepositorySkipListStorageUnitTest {
Assert.assertEquals(true, startTime <endTime)
}
@Test
fun messageRepositorySkipListStorage_indexOfTest() {
val storage = MessageRepository.createSkipListBackedRepository()
val numberOfTestMessages = 50
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
Assert.assertEquals(
1,
storage.indexOf(
MessageInfoModel(
id = "2",
content = "",
messageType = ChatMessageType.TEXT
)
)
)
}
}

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

@ -172,4 +172,31 @@ class MessageRepositoryTreeStorageUnitTest {
Assert.assertEquals(true, startTime <endTime)
}
@Test
fun messageRepositoryTreeStorage_indexOfTest() {
val storage = MessageRepository.createTreeBackedRepository()
val numberOfTestMessages = 50
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
Assert.assertEquals(
1,
storage.indexOf(
MessageInfoModel(
id = "2",
content = "",
messageType = ChatMessageType.TEXT
)
)
)
}
}

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

@ -52,29 +52,6 @@ android {
viewBinding true
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
manifestPlaceholders = [
appIcon : "@mipmap/ic_launcher",
appIconRound: "@mipmap/ic_launcher_round"
]
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'acs-ui-library.pro'
if (file(String.valueOf(System.getenv("KEYSTORE_FILEPATH"))).canRead()) {
signingConfig signingConfigs.release
}
}
debug {
applicationIdSuffix = ".debug"
manifestPlaceholders = [
appIcon : "@mipmap/ic_launcher_debug",
appIconRound: "@mipmap/ic_launcher_debug_round"
]
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@ -122,21 +99,84 @@ android {
flavorDimensions "product"
productFlavors {
calling {
minSdkVersion 21
dimension "product"
matchingFallbacks = ["calling"]
buildTypes {
release {
minifyEnabled true
shrinkResources true
manifestPlaceholders = [
appIcon : "@mipmap/ic_launcher",
appIconRound: "@mipmap/ic_launcher_round"
]
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'acs-ui-library.pro'
if (file(String.valueOf(System.getenv("KEYSTORE_FILEPATH"))).canRead()) {
signingConfig signingConfigs.release
}
}
debug {
applicationIdSuffix = ".debug"
manifestPlaceholders = [
appIcon : "@mipmap/ic_launcher_debug",
appIconRound: "@mipmap/ic_launcher_debug_round"
]
}
}
}
chat {
minSdkVersion 23
dimension "product"
matchingFallbacks = ["chat"]
buildTypes {
release {
minifyEnabled false
shrinkResources false
manifestPlaceholders = [
appIcon : "@mipmap/ic_launcher",
appIconRound: "@mipmap/ic_launcher_round"
]
if (file(String.valueOf(System.getenv("KEYSTORE_FILEPATH"))).canRead()) {
signingConfig signingConfigs.release
}
}
debug {
applicationIdSuffix = ".debug"
manifestPlaceholders = [
appIcon : "@mipmap/ic_launcher_debug",
appIconRound: "@mipmap/ic_launcher_debug_round"
]
}
}
}
callwithchat {
minSdkVersion 23
dimension "product"
matchingFallbacks = ["callwithchat"]
buildTypes {
release {
minifyEnabled false
shrinkResources false
manifestPlaceholders = [
appIcon : "@mipmap/ic_launcher",
appIconRound: "@mipmap/ic_launcher_round"
]
if (file(String.valueOf(System.getenv("KEYSTORE_FILEPATH"))).canRead()) {
signingConfig signingConfigs.release
}
}
debug {
applicationIdSuffix = ".debug"
manifestPlaceholders = [
appIcon : "@mipmap/ic_launcher_debug",
appIconRound: "@mipmap/ic_launcher_debug_round"
]
}
}
}
}
packagingOptions {

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

@ -11,5 +11,6 @@
android:text="TextView"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold" />
android:textStyle="bold"
/>

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

@ -9,8 +9,24 @@
### Bug Fixes
- N/A
## 1.1.0 (2022-11-09)
### New Features
- `CallCompositeSetupScreenViewData` introduced for setting up call title and subtitle.
- New error message `cameraFailure` added to address camera related errors.
- Joining call is prevented with a new Error message now when network is not available.
- Added permission setting capability to allow user to quickly navigate to app's info page when permissions are denied.
### Bug Fixes
- Fixed Error banner and banner text color for dark theme.
- Display DrawerDialog across screen rotation.
- Fix ANR when trying to hang up call on hold.
- Fix edge case with multiple activity instances.
- Fix display name not getting truncated in participant list when they are too long.
## 1.1.0-beta.1 (2022-10-03)
### Features
### New Features
- Setting up Call Title and Subtitle is now availble by customizing `CallCompositeLocalOptions` with `CallCompositeSetupScreenViewData`.
- Implemented new error message `cameraFailure` that can be sent to developers when initiating or turning on camera fails.
- Error message now shown when network is not available before joining a call.
@ -20,7 +36,7 @@
- Display DrawerDialog across screen rotation.
- Fix ANR when trying to hang up call on hold.
- Fix edge case with multiple activity instances.
- Fix display name not getting truncated in participant list when they are too long (https://github.com/Azure/communication-ui-library-android/pull/370).
- Fix display name not getting truncated in participant list when they are too long
## 1.0.0 (2022-06-20)
- This version is the public GA release with Calling UI Library