Skip to content

Commit

Permalink
Added support for interactive messages
Browse files Browse the repository at this point in the history
  • Loading branch information
mrajatttt committed Oct 9, 2024
1 parent 4bd876b commit 12ec6a0
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,10 @@ class ChatViewModel @Inject constructor(
}

fun endChat(){
clearParticipantToken()
viewModelScope.launch {
chatSession.disconnect()
}
clearParticipantToken()
}

fun clearErrorMessage() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ fun ChatMessageView(
}
is QuickReplyContent -> QuickReplyContentView(transcriptItem, content)
is ListPickerContent -> ListPickerContentView(transcriptItem, content)
else -> Text(text = "Unsupported message type")
else -> Text(text = "Unsupported message type, View is missing")
}
}
MessageDirection.COMMON -> CommonChatBubble(transcriptItem)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.amazon.connect.chat.androidchatexample.Config
import com.amazon.connect.chat.androidchatexample.viewmodel.ChatViewModel
Expand Down Expand Up @@ -52,8 +53,17 @@ fun ConfigPicker(viewModel: ChatViewModel) {
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Note: If you see Participant Token available, DO NOT change your account from dropdown, just start the chat to resume previous session",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.padding(bottom = 36.dp).fillMaxWidth().align(Alignment.CenterHorizontally),
color = Color.Red,
textAlign = TextAlign.Center
)

Text("Select Configuration", style = MaterialTheme.typography.bodyLarge)


// Exposed dropdown menu for selecting configuration
ExposedDropdownMenuBox(
expanded = expanded,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import coil.compose.AsyncImage
Expand All @@ -49,21 +50,44 @@ fun ListPickerContentView(
) {
var showListPicker by remember { mutableStateOf(true) }
val viewModel: ChatViewModel = hiltViewModel()
Column(
modifier = Modifier
.padding(8.dp)
.background(Color.White, RoundedCornerShape(8.dp))
.fillMaxWidth(0.80f),
horizontalAlignment = Alignment.End,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 2.dp),
verticalAlignment = Alignment.CenterVertically
) {
message.displayName?.let {
Text(
text = it.ifEmpty { message.participant },
color = Color.Black,
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.weight(1f)
.padding(end = 4.dp),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
Text(
text = CommonUtils.formatTime(message.timeStamp) ?: "",
color = Color.Gray,
style = MaterialTheme.typography.bodySmall
)
}

if (message.participant != null) {
Text(
text = message.participant!!,
style = MaterialTheme.typography.bodySmall,
modifier = Modifier.padding(start = 8.dp, bottom = 4.dp)
)
}
if (showListPicker) {
Column(
modifier = Modifier
.padding(start = 8.dp)
.clip(RoundedCornerShape(size = 10.dp))
.widthIn(max = LocalConfiguration.current.screenWidthDp.dp * 0.75f)
.background(color = Color(0xFF9FB980))
.background(color = Color(0xFFEDEDED))
.padding(horizontal = 10.dp, vertical = 10.dp)
) {
if (!content.imageUrl.isNullOrEmpty()) {
Expand Down Expand Up @@ -101,29 +125,22 @@ fun ListPickerContentView(
}
}else {
Surface(
color = Color(0xFF8BC34A),
color = Color(0xFFEDEDED),
shape = RoundedCornerShape(10.dp),
modifier = Modifier
.padding(start = 8.dp)
.background(Color(0xFF8BC34A), shape = RoundedCornerShape(10.dp))
.widthIn(max = LocalConfiguration.current.screenWidthDp.dp * 0.75f)
.background(Color(0xFFEDEDED), shape = RoundedCornerShape(10.dp))
.widthIn(max = LocalConfiguration.current.screenWidthDp.dp * 0.80f)
) {
Column(modifier = Modifier.padding(10.dp)) {
Column(modifier = Modifier.padding(10.dp).fillMaxWidth(),
horizontalAlignment = Alignment.Start) {
Text(
text = content.title,
color = Color.White
color = Color.Black
)
message.timeStamp?.let {
Text(
text = CommonUtils.formatTime(it),
style = MaterialTheme.typography.bodySmall,
color = Color.White,
modifier = Modifier.align(Alignment.End).alpha(0.7f)
)
}
}
}
}
}
}

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,11 @@ data class Message(
val jsonData = text.toByteArray(Charsets.UTF_8)
val genericTemplate = Json { ignoreUnknownKeys = true }.decodeFromString<GenericInteractiveTemplate>(String(jsonData))
when (genericTemplate.templateType) {
QuickReplyContent.templateType -> QuickReplyContent.decode(text)
ListPickerContent.templateType -> ListPickerContent.decode(text)
// Add cases for each interactive message type, decoding as appropriate.
QuickReplyContent.TEMPLATE_TYPE -> QuickReplyContent.decode(text)
ListPickerContent.TEMPLATE_TYPE -> ListPickerContent.decode(text)
TimePickerContent.TEMPLATE_TYPE -> TimePickerContent.decode(text)
CarouselContent.TEMPLATE_TYPE -> CarouselContent.decode(text)
PanelContent.TEMPLATE_TYPE -> PanelContent.decode(text)
else -> {
logUnsupportedContentType(genericTemplate.templateType)
null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.amazon.connect.chat.sdk.model

import com.amazon.connect.chat.sdk.utils.Constants
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json

interface MessageContent {
Expand Down Expand Up @@ -52,7 +54,7 @@ data class QuickReplyContent(
val options: List<String>
) : InteractiveContent {
companion object {
const val templateType = "QuickReply"
const val TEMPLATE_TYPE = Constants.QUICK_REPLY
fun decode(text: String): InteractiveContent? {
return try {
val quickReply = Json.decodeFromString<QuickReplyTemplate>(text)
Expand Down Expand Up @@ -102,7 +104,7 @@ data class ListPickerContent(
val options: List<ListPickerElement>
) : InteractiveContent {
companion object {
const val templateType = "ListPicker"
const val TEMPLATE_TYPE = Constants.LIST_PICKER
fun decode(text: String): InteractiveContent? {
return try {
val listPicker = Json.decodeFromString<ListPickerTemplate>(text)
Expand All @@ -118,3 +120,187 @@ data class ListPickerContent(
}
}
}


// Time Picker
@Serializable
data class TimeSlot(
val date: String,
val duration: Int
)

@Serializable
data class Location(
val latitude: Double,
val longitude: Double,
val title: String,
val radius: Int? = null
)

@Serializable
data class TimePickerContentData(
val title: String,
val subtitle: String? = null,
val timeZoneOffset: Int? = null,
val location: Location? = null,
val timeslots: List<TimeSlot>
)

@Serializable
data class TimePickerReplyMessage(
val title: String? = null,
val subtitle: String? = null
)

@Serializable
data class TimePickerData(
val replyMessage: TimePickerReplyMessage? = null,
val content: TimePickerContentData
)

@Serializable
data class TimePickerTemplate(
val templateType: String,
val version: String,
val data: TimePickerData
)

data class TimePickerContent(
val title: String,
val subtitle: String? = null,
val timeZoneOffset: Int? = null,
val location: Location? = null,
val timeslots: List<TimeSlot>
): InteractiveContent {
companion object {
const val TEMPLATE_TYPE = Constants.TIME_PICKER
fun decode(text: String): InteractiveContent? {
return try {
val timePicker = Json.decodeFromString<TimePickerTemplate>(text)
val contentData = timePicker.data.content
TimePickerContent(
title = contentData.title,
subtitle = contentData.subtitle,
timeZoneOffset = contentData.timeZoneOffset,
location = contentData.location,
timeslots = contentData.timeslots
)
} catch (e: SerializationException) {
println("Error decoding TimePickerContent: ${e.localizedMessage}")
null
}
}
}
}

// Carousel
@Serializable
data class CarouselElement(
val templateIdentifier: String,
val templateType: String,
val version: String,
val data: PanelData
)

@Serializable
data class CarouselContentData(
val title: String,
val elements: List<CarouselElement>
)

@Serializable
data class CarouselData(
val content: CarouselContentData
)

@Serializable
data class CarouselTemplate(
val templateType: String,
val version: String,
val data: CarouselData
)

data class CarouselContent(
val title: String,
val elements: List<CarouselElement>
): InteractiveContent {
companion object {
const val TEMPLATE_TYPE = Constants.CAROUSEL
fun decode(text: String): InteractiveContent? {
return try {
val carousel = Json.decodeFromString<CarouselTemplate>(text)
val contentData = carousel.data.content
CarouselContent(
title = contentData.title,
elements = contentData.elements
)
} catch (e: SerializationException) {
println("Error decoding CarouselContent: ${e.localizedMessage}")
null
}
}
}
}

// Panel
@Serializable
data class PanelElement(
val title: String
)

@Serializable
data class PanelContentData(
val title: String,
val subtitle: String? = null,
val imageType: String? = null,
val imageData: String? = null,
val imageDescription: String? = null,
val elements: List<PanelElement>
)

@Serializable
data class PanelReplyMessage(
val title: String,
val subtitle: String? = null
)

@Serializable
data class PanelData(
val replyMessage: PanelReplyMessage? = null,
val content: PanelContentData
)

@Serializable
data class PanelTemplate(
val templateType: String,
val version: String,
val data: PanelData
)

data class PanelContent(
val title: String,
val subtitle: String? = null,
val imageUrl: String? = null,
val imageDescription: String? = null,
val options: List<PanelElement>
): InteractiveContent {
companion object {
const val TEMPLATE_TYPE = Constants.PANEL
fun decode(text: String): InteractiveContent? {
return try {
val panel = Json.decodeFromString<PanelTemplate>(text)
val contentData = panel.data.content
PanelContent(
title = contentData.title,
subtitle = contentData.subtitle,
imageUrl = contentData.imageData,
imageDescription = contentData.imageDescription,
options = contentData.elements
)
} catch (e: SerializationException) {
println("Error decoding PanelContent: ${e.localizedMessage}")
null
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,6 @@ class MessageReceiptsManagerImpl : MessageReceiptsManager {
pendingMessageReceipts.checkAndRemoveDuplicateReceipt()
continuation.resume(Result.success(pendingMessageReceipts))
} catch (e: Exception) {
SDKLogger.logger.logError { "Error during throttling: ${e.message}" }
continuation.resumeWithException(e)
}
}
Expand All @@ -108,9 +107,7 @@ class MessageReceiptsManagerImpl : MessageReceiptsManager {
CoroutineScope(Dispatchers.Default).launch {
numPendingDeliveredReceipts++
delay((deliveredThrottleTime * 1000).toLong())
if (readReceiptSet.contains(messageId)) {
SDKLogger.logger.logDebug { "Read receipt already sent for messageId: $messageId" }
} else {
if (!readReceiptSet.contains(messageId)) {
pendingMessageReceipts.deliveredReceiptMessageId = messageId
}
numPendingDeliveredReceipts--
Expand Down

0 comments on commit 12ec6a0

Please sign in to comment.