Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ android {

dependencies {
// Twilio
implementation "com.twilio:conversations-android-with-symbols:6.0.3"
implementation "com.twilio:conversations-android-with-symbols:6.2.0"
// or without symbols:
// implementation "com.twilio:conversations-android:6.0.3"

Expand All @@ -132,6 +132,7 @@ dependencies {
// Room components
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
androidTestImplementation project(':app')
kapt "androidx.room:room-compiler:$room_version"

// Material
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,8 +510,8 @@ class MessageListActivityTest {
// Validate media messages
if (message.type == MessageType.MEDIA) {
val mediaMatcher = when {
message.mediaDownloadState == DOWNLOADING -> hasDescendant(withId(R.id.attachment_progress))
message.mediaDownloadState == COMPLETED -> hasDescendant(withText(R.string.attachment_tap_to_open))
message.attachmentsList.first().downloadState == DOWNLOADING -> hasDescendant(withId(R.id.attachment_progress))
message.attachmentsList.first().downloadState == COMPLETED -> hasDescendant(withText(R.string.attachment_tap_to_open))
else -> hasDescendant(
withText(
Formatter.formatShortFileSize(
Expand All @@ -532,7 +532,7 @@ class MessageListActivityTest {
hasSibling(
allOf(
withId(R.id.attachment_file_name),
withText(message.mediaFileName)
withText(message.attachmentsList.first().fileName)
)
)
)
Expand All @@ -541,7 +541,7 @@ class MessageListActivityTest {
allOf(
withId(R.id.attachment_progress),
withEffectiveVisibility(
if (message.mediaDownloadState == DOWNLOADING)
if (message.attachmentsList.first().downloadState == DOWNLOADING)
Visibility.VISIBLE else Visibility.GONE
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import android.text.format.Formatter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ProgressBar
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.databinding.ViewDataBinding
import androidx.paging.PagedListAdapter
Expand All @@ -16,18 +19,17 @@ import com.twilio.conversations.app.common.enums.Direction
import com.twilio.conversations.app.common.enums.DownloadState.COMPLETED
import com.twilio.conversations.app.common.enums.DownloadState.DOWNLOADING
import com.twilio.conversations.app.common.enums.DownloadState.ERROR
import com.twilio.conversations.app.common.enums.DownloadState.NOT_STARTED
import com.twilio.conversations.app.common.enums.Reaction
import com.twilio.conversations.app.common.enums.SendStatus
import com.twilio.conversations.app.data.models.MessageListViewItem
import com.twilio.conversations.app.data.models.MessageAttachmentViewItem
import com.twilio.conversations.app.databinding.RowMessageItemIncomingBinding
import com.twilio.conversations.app.databinding.RowMessageItemOutgoingBinding
import com.twilio.conversations.app.databinding.ViewReactionItemBinding
import timber.log.Timber

class MessageListAdapter(
private val onDisplaySendError: (message: MessageListViewItem) -> Unit,
private val onDownloadMedia: (message: MessageListViewItem) -> Unit,
private val onDownloadMedia: (message: MessageListViewItem, media: MessageAttachmentViewItem) -> Unit,
private val onOpenMedia: (location: Uri, mimeType: String) -> Unit,
private val onItemLongClick: (messageIndex: Long) -> Unit,
private val onReactionClicked: (messageIndex: Long) -> Unit
Expand Down Expand Up @@ -63,52 +65,6 @@ class MessageListAdapter(
val binding = holder.binding
val context = binding.root.context

val mediaSize = Formatter.formatShortFileSize(context, message.mediaSize ?: 0)
val mediaUploadedBytes = Formatter.formatShortFileSize(context, message.mediaUploadedBytes ?: 0)
val mediaDownloadedBytes = Formatter.formatShortFileSize(context, message.mediaDownloadedBytes ?: 0)

val attachmentInfoText = when {
message.sendStatus == SendStatus.ERROR -> context.getString(R.string.err_failed_to_upload_media)

message.mediaUploading -> context.getString(R.string.attachment_uploading, mediaUploadedBytes)

message.mediaUploadUri != null ||
message.mediaDownloadState == COMPLETED -> context.getString(R.string.attachment_tap_to_open)

message.mediaDownloadState == NOT_STARTED -> mediaSize

message.mediaDownloadState == DOWNLOADING -> context.getString(
R.string.attachment_downloading,
mediaDownloadedBytes
)

message.mediaDownloadState == ERROR -> context.getString(R.string.err_failed_to_download_media)

else -> error("Never happens")
}

val attachmentInfoColor = when {
message.sendStatus == SendStatus.ERROR ||
message.mediaDownloadState == ERROR -> ContextCompat.getColor(context, R.color.colorAccent)

message.mediaUploading -> ContextCompat.getColor(context, R.color.text_subtitle)

message.mediaUploadUri != null ||
message.mediaDownloadState == COMPLETED -> ContextCompat.getColor(context, R.color.colorPrimary)

else -> ContextCompat.getColor(context, R.color.text_subtitle)
}

val attachmentOnClickListener = View.OnClickListener {
if (message.mediaDownloadState == COMPLETED && message.mediaUri != null) {
onOpenMedia(message.mediaUri, message.mediaType!!)
} else if (message.mediaUploadUri != null) {
onOpenMedia(message.mediaUploadUri, message.mediaType!!)
} else if (message.mediaDownloadState != DOWNLOADING) {
onDownloadMedia(message)
}
}

val longClickListener = View.OnLongClickListener {
onItemLongClick(message.index)
return@OnLongClickListener true
Expand All @@ -126,22 +82,102 @@ class MessageListAdapter(
is RowMessageItemIncomingBinding -> {
binding.message = message
addReactions(binding.messageReactionHolder, message)
binding.attachmentInfo.text = attachmentInfoText
binding.attachmentInfo.setTextColor(attachmentInfoColor)
binding.attachmentBackground.setOnClickListener(attachmentOnClickListener)
binding.attachmentBackground.setOnLongClickListener(longClickListener)
setupAttachments(binding.attachmentsContainer, message, longClickListener)
}
is RowMessageItemOutgoingBinding -> {
binding.message = message
addReactions(binding.messageReactionHolder, message)
binding.attachmentInfo.text = attachmentInfoText
binding.attachmentInfo.setTextColor(attachmentInfoColor)
binding.attachmentBackground.setOnClickListener(attachmentOnClickListener)
binding.attachmentBackground.setOnLongClickListener(longClickListener)
setupAttachments(binding.attachmentsContainer, message, longClickListener)
}
else -> error("Unknown binding type: $binding")
}
}

private fun setupAttachments(
attachmentsContainer: LinearLayout,
message: MessageListViewItem,
longClickListener: View.OnLongClickListener
) {
attachmentsContainer.removeAllViews()

message.attachmentsList.forEach { attachment ->
val attachmentView = LayoutInflater.from(attachmentsContainer.context)
.inflate(R.layout.item_attachment, attachmentsContainer, false)

val icon = attachmentView.findViewById<ImageView>(R.id.attachment_icon)
val fileName = attachmentView.findViewById<TextView>(R.id.attachment_file_name)
val info = attachmentView.findViewById<TextView>(R.id.attachment_info)
val progress = attachmentView.findViewById<ProgressBar>(R.id.attachment_progress)
val failed = attachmentView.findViewById<ImageView>(R.id.attachment_failed)

fileName.text = attachment.fileName ?: "Unknown file"
val size = Formatter.formatShortFileSize(attachmentsContainer.context, attachment.size ?: 0)

when {
message.sendStatus == SendStatus.ERROR -> {
info.text = attachmentsContainer.context.getString(R.string.err_failed_to_upload_media)
info.setTextColor(ContextCompat.getColor(attachmentsContainer.context, R.color.colorAccent))
failed.visibility = View.VISIBLE
progress.visibility = View.GONE
icon.setImageResource(R.drawable.ic_attachment_to_download)
}
attachment.downloadState == ERROR -> {
info.text = attachmentsContainer.context.getString(R.string.err_failed_to_download_media)
info.setTextColor(ContextCompat.getColor(attachmentsContainer.context, R.color.colorAccent))
failed.visibility = View.VISIBLE
progress.visibility = View.GONE
icon.setImageResource(R.drawable.ic_attachment_to_download)
}
attachment.downloadState == DOWNLOADING -> {
val downloadedBytes = Formatter.formatShortFileSize(attachmentsContainer.context, attachment.downloadedBytes ?: 0)
info.text = attachmentsContainer.context.getString(R.string.attachment_downloading, downloadedBytes)
info.setTextColor(ContextCompat.getColor(attachmentsContainer.context, R.color.text_subtitle))
progress.visibility = View.VISIBLE
failed.visibility = View.GONE
icon.setImageResource(R.drawable.ic_attachment_to_download)
}
attachment.uploading -> {
val uploadedBytes = Formatter.formatShortFileSize(attachmentsContainer.context, attachment.uploadedBytes ?: 0)
info.text = attachmentsContainer.context.getString(R.string.attachment_uploading, uploadedBytes)
info.setTextColor(ContextCompat.getColor(attachmentsContainer.context, R.color.text_subtitle))
progress.visibility = View.VISIBLE
failed.visibility = View.GONE
icon.setImageResource(R.drawable.ic_attachment_to_download)
}
attachment.downloadState == COMPLETED || attachment.uploadUri != null -> {
info.text = attachmentsContainer.context.getString(R.string.attachment_tap_to_open)
info.setTextColor(ContextCompat.getColor(attachmentsContainer.context, R.color.colorPrimary))
progress.visibility = View.GONE
failed.visibility = View.GONE
icon.setImageResource(R.drawable.ic_attachment_downloaded)
}
else -> {
info.text = size
info.setTextColor(ContextCompat.getColor(attachmentsContainer.context, R.color.text_subtitle))
progress.visibility = View.GONE
failed.visibility = View.GONE
icon.setImageResource(R.drawable.ic_attachment_to_download)
}
}

attachmentView.setOnClickListener {
when {
attachment.downloadState == COMPLETED && attachment.uri != null -> {
onOpenMedia(attachment.uri, attachment.type ?: "")
}
attachment.uploadUri != null -> {
onOpenMedia(attachment.uploadUri, attachment.type ?: "")
}
attachment.downloadState != DOWNLOADING -> {
onDownloadMedia(message, attachment)
}
}
}

attachmentView.setOnLongClickListener(longClickListener)

attachmentsContainer.addView(attachmentView)
}
}

private fun addReactions(rootView: LinearLayout, message: MessageListViewItem) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import com.twilio.conversations.Message
import com.twilio.conversations.Participant
import com.twilio.conversations.User
import com.twilio.conversations.app.common.enums.Direction
import com.twilio.conversations.app.common.enums.DownloadState
import com.twilio.conversations.app.common.enums.MessageType
import com.twilio.conversations.app.common.enums.Reaction
import com.twilio.conversations.app.common.enums.Reactions
Expand All @@ -24,6 +23,7 @@ import com.twilio.conversations.app.common.extensions.asMessageDateString
import com.twilio.conversations.app.common.extensions.firstMedia
import com.twilio.conversations.app.data.localCache.entity.ConversationDataItem
import com.twilio.conversations.app.data.localCache.entity.MessageDataItem
import com.twilio.conversations.app.data.localCache.entity.MessageAttachmentDataItem
import com.twilio.conversations.app.data.localCache.entity.ParticipantDataItem
import com.twilio.conversations.app.data.models.*
import com.twilio.conversations.app.manager.friendlyName
Expand Down Expand Up @@ -63,10 +63,15 @@ fun Message.toMessageDataItem(currentUserIdentity: String = participant.identity
if (this.author == currentUserIdentity) Direction.OUTGOING.value else Direction.INCOMING.value,
if (this.author == currentUserIdentity) SendStatus.SENT.value else SendStatus.UNDEFINED.value,
uuid,
media?.sid,
media?.filename,
media?.contentType,
media?.size
attachmentsList = this.attachedMedia.map { mediaItem ->
MessageAttachmentDataItem(
sid = mediaItem.sid,
fileName = mediaItem.filename,
type = mediaItem.contentType,
size = mediaItem.size
)
},
mediaSize = this.attachedMedia.sumOf { it.size }
)
}

Expand All @@ -84,17 +89,22 @@ fun MessageDataItem.toMessageListViewItem(authorChanged: Boolean): MessageListVi
sendStatusIcon = SendStatus.fromInt(this.sendStatus).asLastMesageStatusIcon(),
getReactions(attributes).asReactionList(),
MessageType.fromInt(this.type),
this.mediaSid,
this.mediaFileName,
this.mediaType,
attachmentsList = this.attachmentsList.map { attachment ->
MessageAttachmentViewItem(
attachment.sid,
attachment.fileName,
attachment.type,
attachment.size,
attachment.uri?.toUri(),
attachment.downloadId,
attachment.downloadedBytes,
attachment.downloadState,
attachment.uploading,
attachment.uploadedBytes,
attachment.uploadUri?.toUri()
)
},
this.mediaSize,
this.mediaUri?.toUri(),
this.mediaDownloadId,
this.mediaDownloadedBytes,
DownloadState.fromInt(this.mediaDownloadState),
this.mediaUploading,
this.mediaUploadedBytes,
this.mediaUploadUri?.toUri(),
this.errorCode
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.twilio.conversations.app.data.localCache.converters.AttachmentsListConverter
import com.twilio.conversations.app.data.localCache.dao.ConversationsDao
import com.twilio.conversations.app.data.localCache.dao.MessagesDao
import com.twilio.conversations.app.data.localCache.dao.ParticipantsDao
Expand All @@ -12,6 +14,7 @@ import com.twilio.conversations.app.data.localCache.entity.MessageDataItem
import com.twilio.conversations.app.data.localCache.entity.ParticipantDataItem

@Database(entities = [ConversationDataItem::class, MessageDataItem::class, ParticipantDataItem::class], version = 1, exportSchema = false)
@TypeConverters(AttachmentsListConverter::class)
abstract class LocalCacheProvider : RoomDatabase() {

abstract fun conversationsDao(): ConversationsDao
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.twilio.conversations.app.data.localCache.converters

import androidx.room.TypeConverter
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.twilio.conversations.app.data.localCache.entity.MessageAttachmentDataItem

class AttachmentsListConverter {
private val gson = Gson()

@TypeConverter
fun fromAttachmentsList(attachmentsList: List<MessageAttachmentDataItem>): String {
return gson.toJson(attachmentsList)
}

@TypeConverter
fun toAttachmentsList(attachmentsListString: String): List<MessageAttachmentDataItem> {
val listType = object : TypeToken<List<MessageAttachmentDataItem>>() {}.type
return gson.fromJson(attachmentsListString, listType) ?: emptyList()
}
}
Loading