diff --git a/app/build.gradle b/app/build.gradle
index bac4966..dd18302 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -66,12 +66,12 @@ android {
compileOptions {
coreLibraryDesugaringEnabled true
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = JavaVersion.VERSION_11.toString()
+ jvmTarget = JavaVersion.VERSION_17.toString()
freeCompilerArgs += [
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.ObsoleteCoroutinesApi",
@@ -105,9 +105,18 @@ android {
}
}
+repositories {
+ google()
+ mavenCentral()
+ flatDir {
+ dirs 'aars'
+ }
+}
+
dependencies {
// Twilio
- implementation "com.twilio:conversations-android-with-symbols:6.0.3"
+// implementation files('aars/convo-android-release.aar')
+ implementation "com.twilio:conversations-android-with-symbols:6.1.1"
// or without symbols:
// implementation "com.twilio:conversations-android:6.0.3"
@@ -202,6 +211,7 @@ tasks.withType(Test).configureEach {
tasks.withType(KotlinCompile).configureEach {
compilerOptions {
- jvmTarget.set(JvmTarget.JVM_11)
+ jvmTarget.set(JvmTarget.JVM_17)
}
}
+
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 499a496..a4ffb85 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -52,14 +52,14 @@
android:name=".ui.ParticipantListActivity"
android:theme="@style/AppTheme.NoActionBar" />
-
-
-
-
-
-
+
+
+
+
+
+
+
+
Unit,
- private val onDownloadMedia: (message: MessageListViewItem) -> Unit,
+ private val onDownloadMedia: (message: MessageListViewItem, media: MessageMediaViewItem) -> Unit,
private val onOpenMedia: (location: Uri, mimeType: String) -> Unit,
private val onItemLongClick: (messageIndex: Long) -> Unit,
private val onReactionClicked: (messageIndex: Long) -> Unit
@@ -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
@@ -125,25 +81,111 @@ class MessageListAdapter(
when (binding) {
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)
+// addReactions(binding.messageReactionHolder, message)
+ updateAttachments(binding.attachmentsContainer, message)
+ binding.attachmentsContainer.setOnLongClickListener(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)
+// addReactions(binding.messageReactionHolder, message)
+ updateAttachments(binding.attachmentsContainer, message)
+ binding.attachmentsContainer.setOnLongClickListener(longClickListener)
}
else -> error("Unknown binding type: $binding")
}
}
+ private fun updateAttachments(
+ attachmentsContainer: android.widget.LinearLayout,
+ message: MessageListViewItem
+ ) {
+ attachmentsContainer.removeAllViews()
+ val context = attachmentsContainer.context
+
+ if (message.mediaData.isEmpty()) {
+ attachmentsContainer.visibility = android.view.View.GONE
+ return
+ }
+ attachmentsContainer.visibility = android.view.View.VISIBLE
+
+ message.mediaData.forEach { mediaItem ->
+ // Inflate a new layout for each attachment
+ val attachmentBinding = RowMessageMediaItemBinding.inflate(
+ LayoutInflater.from(context),
+ attachmentsContainer,
+ false // Attach manually below
+ )
+
+ // Determine text and color for this specific media item
+ val mediaSize = mediaItem.mediaSize?.let { Formatter.formatShortFileSize(context, it) }
+ val mediaUploadedBytes =
+ Formatter.formatShortFileSize(context, mediaItem.mediaUploadedBytes ?: 0)
+ val mediaDownloadedBytes =
+ Formatter.formatShortFileSize(context, mediaItem.mediaDownloadedBytes ?: 0)
+
+ attachmentBinding.attachmentFileName.text = mediaItem.mediaFileName ?: "Attachment"
+
+ val attachmentInfoText = when {
+ message.sendStatus == SendStatus.ERROR -> context.getString(R.string.err_failed_to_upload_media)
+ mediaItem.mediaUploading -> context.getString(
+ R.string.attachment_uploading,
+ mediaUploadedBytes
+ )
+
+ mediaItem.mediaUploadUri != null || mediaItem.mediaDownloadState == COMPLETED -> context.getString(
+ R.string.attachment_tap_to_open
+ )
+
+ mediaItem.mediaDownloadState == NOT_STARTED -> mediaSize
+ mediaItem.mediaDownloadState == DOWNLOADING -> context.getString(
+ R.string.attachment_downloading,
+ mediaDownloadedBytes
+ )
+
+ mediaItem.mediaDownloadState == ERROR -> context.getString(R.string.err_failed_to_download_media)
+ else -> ""
+ }
+
+ val attachmentInfoColor = when {
+ message.sendStatus == SendStatus.ERROR || mediaItem.mediaDownloadState == ERROR ->
+ ContextCompat.getColor(context, R.color.colorAccent)
+
+ mediaItem.mediaUploading || mediaItem.mediaDownloadState == DOWNLOADING ->
+ ContextCompat.getColor(context, R.color.text_subtitle)
+
+ mediaItem.mediaUploadUri != null || mediaItem.mediaDownloadState == COMPLETED ->
+ ContextCompat.getColor(context, R.color.colorPrimary)
+
+ else -> ContextCompat.getColor(context, R.color.text_subtitle)
+ }
+
+ attachmentBinding.attachmentInfo.text = attachmentInfoText
+ attachmentBinding.attachmentInfo.setTextColor(attachmentInfoColor)
+
+ // Set click listener for this specific attachment
+ attachmentBinding.root.setOnClickListener {
+ when {
+ mediaItem.mediaDownloadState == COMPLETED && mediaItem.mediaUri != null ->
+ mediaItem.mediaType?.let { it1 -> onOpenMedia(mediaItem.mediaUri, it1) }
+
+ mediaItem.mediaUploadUri != null ->
+ mediaItem.mediaType?.let { it1 ->
+ onOpenMedia(mediaItem.mediaUploadUri,
+ it1
+ )
+ }
+
+ mediaItem.mediaDownloadState != DOWNLOADING && !mediaItem.mediaUploading ->
+ onDownloadMedia(message, mediaItem) // Pass the specific media item
+ }
+ }
+
+ // Add the newly created attachment view to the container
+ attachmentsContainer.addView(attachmentBinding.root)
+ }
+ }
+
private fun addReactions(rootView: LinearLayout, message: MessageListViewItem) {
rootView.setOnClickListener { onReactionClicked(message.index) }
rootView.removeAllViews()
diff --git a/app/src/main/java/com/twilio/conversations/app/common/DataConverter.kt b/app/src/main/java/com/twilio/conversations/app/common/DataConverter.kt
index 2316bff..cf8d8c4 100644
--- a/app/src/main/java/com/twilio/conversations/app/common/DataConverter.kt
+++ b/app/src/main/java/com/twilio/conversations/app/common/DataConverter.kt
@@ -23,6 +23,7 @@ import com.twilio.conversations.app.common.extensions.asMessageCount
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.MediaDataItem
import com.twilio.conversations.app.data.localCache.entity.MessageDataItem
import com.twilio.conversations.app.data.localCache.entity.ParticipantDataItem
import com.twilio.conversations.app.data.models.*
@@ -49,12 +50,11 @@ fun Conversation.toConversationDataItem(): ConversationDataItem {
}
fun Message.toMessageDataItem(currentUserIdentity: String = participant.identity, uuid: String = ""): MessageDataItem {
- val media = firstMedia // @todo: support multiple media
return MessageDataItem(
this.sid,
this.conversationSid,
this.participantSid,
- if (media != null) MessageType.MEDIA.value else MessageType.TEXT.value,
+ if (this.attachedMedia.isNotEmpty()) MessageType.MEDIA.value else MessageType.TEXT.value,
this.author,
this.dateCreatedAsDate.time,
this.body ?: "",
@@ -62,15 +62,11 @@ fun Message.toMessageDataItem(currentUserIdentity: String = participant.identity
this.attributes.toString(),
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
+ uuid
)
}
-fun MessageDataItem.toMessageListViewItem(authorChanged: Boolean): MessageListViewItem {
+fun MessageDataItem.toMessageListViewItem(authorChanged: Boolean, mediaList: List? = null): MessageListViewItem {
return MessageListViewItem(
this.sid,
this.uuid,
@@ -84,17 +80,20 @@ 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,
- this.mediaSize,
- this.mediaUri?.toUri(),
- this.mediaDownloadId,
- this.mediaDownloadedBytes,
- DownloadState.fromInt(this.mediaDownloadState),
- this.mediaUploading,
- this.mediaUploadedBytes,
- this.mediaUploadUri?.toUri(),
+ mediaData= mediaList?.map { media -> MessageMediaViewItem(
+ media.mediaSid,
+ media.mediaFileName,
+ media.mediaType,
+ media.mediaSize,
+ media.mediaUri?.toUri(),
+ media.mediaDownloadId,
+ media.mediaDownloadedBytes,
+ DownloadState.fromInt(media.mediaDownloadState),
+ media.mediaUploading,
+ media.mediaUploadedBytes,
+ media.mediaUploadUri?.toUri()
+ )
+ } ?: emptyList(),
this.errorCode
)
}
diff --git a/app/src/main/java/com/twilio/conversations/app/data/localCache/LocalCacheProvider.kt b/app/src/main/java/com/twilio/conversations/app/data/localCache/LocalCacheProvider.kt
index 6178858..3c78d46 100644
--- a/app/src/main/java/com/twilio/conversations/app/data/localCache/LocalCacheProvider.kt
+++ b/app/src/main/java/com/twilio/conversations/app/data/localCache/LocalCacheProvider.kt
@@ -5,13 +5,15 @@ import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import com.twilio.conversations.app.data.localCache.dao.ConversationsDao
+import com.twilio.conversations.app.data.localCache.dao.MediaDao
import com.twilio.conversations.app.data.localCache.dao.MessagesDao
import com.twilio.conversations.app.data.localCache.dao.ParticipantsDao
import com.twilio.conversations.app.data.localCache.entity.ConversationDataItem
+import com.twilio.conversations.app.data.localCache.entity.MediaDataItem
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)
+@Database(entities = [ConversationDataItem::class, MessageDataItem::class, ParticipantDataItem::class, MediaDataItem::class], version = 1, exportSchema = false)
abstract class LocalCacheProvider : RoomDatabase() {
abstract fun conversationsDao(): ConversationsDao
@@ -20,6 +22,8 @@ abstract class LocalCacheProvider : RoomDatabase() {
abstract fun participantsDao(): ParticipantsDao
+ abstract fun mediaDao(): MediaDao
+
companion object {
val INSTANCE get() = _instance ?: error("call LocalCacheProvider.createInstance() first")
diff --git a/app/src/main/java/com/twilio/conversations/app/data/localCache/dao/MediaDao.kt b/app/src/main/java/com/twilio/conversations/app/data/localCache/dao/MediaDao.kt
new file mode 100644
index 0000000..51873db
--- /dev/null
+++ b/app/src/main/java/com/twilio/conversations/app/data/localCache/dao/MediaDao.kt
@@ -0,0 +1,38 @@
+package com.twilio.conversations.app.data.localCache.dao
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+import com.twilio.conversations.app.data.localCache.entity.MediaDataItem
+
+
+@Dao
+interface MediaDao {
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insert(mediaList: List)
+
+ @Query("SELECT * FROM media_table WHERE messageSid = :messageSid")
+ fun getAllMediaByMessageSid(messageSid: String): List?
+
+ @Query("SELECT * FROM media_table WHERE messageUuid = :messageUuid")
+ fun getAllMediaByMessageUuid(messageUuid: String): List?
+
+ @Query("UPDATE media_table SET mediaDownloadState = :downloadState WHERE mediaSid = :mediaSid")
+ fun updateMediaDownloadState(mediaSid: String, downloadState: Int)
+
+ @Query("UPDATE media_table SET mediaDownloadedBytes = :downloadedBytes WHERE mediaSid = :mediaSid")
+ fun updateMediaDownloadedBytes(mediaSid: String, downloadedBytes: Long)
+
+ @Query("UPDATE media_table SET mediaUri = :location WHERE mediaSid = :mediaSid")
+ fun updateMediaDownloadLocation(mediaSid: String, location: String)
+
+ @Query("UPDATE media_table SET mediaDownloadId = :downloadId WHERE mediaSid = :mediaSid")
+ fun updateMediaDownloadId(mediaSid: String, downloadId: Long)
+
+ @Query("UPDATE media_table SET mediaUploading = :downloading WHERE mediaSid = :mediaSid")
+ fun updateMediaUploadStatus(mediaSid: String, downloading: Boolean)
+
+ @Query("UPDATE media_table SET mediaUploadedBytes = :downloadedBytes WHERE mediaSid = :mediaSid")
+ fun updateMediaUploadedBytes(mediaSid: String, downloadedBytes: Long)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/twilio/conversations/app/data/localCache/dao/MessagesDao.kt b/app/src/main/java/com/twilio/conversations/app/data/localCache/dao/MessagesDao.kt
index b3c4db1..2e15e92 100644
--- a/app/src/main/java/com/twilio/conversations/app/data/localCache/dao/MessagesDao.kt
+++ b/app/src/main/java/com/twilio/conversations/app/data/localCache/dao/MessagesDao.kt
@@ -41,13 +41,13 @@ interface MessagesDao {
fun updateMessageStatus(uuid: String, sendStatus: Int, errorCode: Int)
// Update single Message
- @Query("UPDATE message_table SET sid = :sid, sendStatus = :sendStatus, `index` = :index, mediaSize = :mediaSize WHERE uuid = :uuid")
- fun updateByUuid(sid: String, uuid: String, sendStatus: Int, index: Long, mediaSize: Long?)
+ @Query("UPDATE message_table SET sid = :sid, sendStatus = :sendStatus, `index` = :index WHERE uuid = :uuid")
+ fun updateByUuid(sid: String, uuid: String, sendStatus: Int, index: Long)
@Transaction
fun updateByUuidOrInsert(message: MessageDataItem) {
if (message.uuid.isNotEmpty() && getMessageByUuid(message.uuid) != null) {
- updateByUuid(message.sid, message.uuid, message.sendStatus, message.index, message.mediaSize)
+ updateByUuid(message.sid, message.uuid, message.sendStatus, message.index)
} else {
insertOrReplace(message)
}
@@ -55,23 +55,4 @@ interface MessagesDao {
@Delete
fun delete(message: MessageDataItem)
-
- @Query("UPDATE message_table SET mediaDownloadState = :downloadState WHERE sid = :messageSid")
- fun updateMediaDownloadState(messageSid: String, downloadState: Int)
-
- @Query("UPDATE message_table SET mediaDownloadedBytes = :downloadedBytes WHERE sid = :messageSid")
- fun updateMediaDownloadedBytes(messageSid: String, downloadedBytes: Long)
-
- @Query("UPDATE message_table SET mediaUri = :location WHERE sid = :messageSid")
- fun updateMediaDownloadLocation(messageSid: String, location: String)
-
- @Query("UPDATE message_table SET mediaDownloadId = :downloadId WHERE sid = :messageSid")
- fun updateMediaDownloadId(messageSid: String, downloadId: Long)
-
- @Query("UPDATE message_table SET mediaUploading = :downloading WHERE uuid = :uuid")
- fun updateMediaUploadStatus(uuid: String, downloading: Boolean)
-
- @Query("UPDATE message_table SET mediaUploadedBytes = :downloadedBytes WHERE uuid = :uuid")
- fun updateMediaUploadedBytes(uuid: String, downloadedBytes: Long)
-
}
diff --git a/app/src/main/java/com/twilio/conversations/app/data/localCache/entity/MediaDataItem.kt b/app/src/main/java/com/twilio/conversations/app/data/localCache/entity/MediaDataItem.kt
new file mode 100644
index 0000000..a176a1d
--- /dev/null
+++ b/app/src/main/java/com/twilio/conversations/app/data/localCache/entity/MediaDataItem.kt
@@ -0,0 +1,60 @@
+package com.twilio.conversations.app.data.localCache.entity
+
+import androidx.room.Ignore
+import java.io.InputStream
+import androidx.room.Entity;
+
+@Entity(tableName = "media_table", primaryKeys = ["mediaSid"])
+data class MediaDataItem(
+ val messageSid: String? = null,
+ val mediaSid: String,
+ val mediaFileName: String? = null,
+ val mediaType: String? = null,
+ val mediaSize: Long? = null,
+ val mediaUri: String? = null,
+ val mediaDownloadId: Long? = null,
+ val mediaDownloadedBytes: Long? = null,
+ val mediaDownloadState: Int = 0,
+ val mediaUploading: Boolean = false,
+ val mediaUploadedBytes: Long? = null,
+ val mediaUploadUri: String? = null,
+ val messageUuid: String
+) {
+ @Ignore
+ @Transient
+ var inputStream: InputStream? = null
+
+ constructor(
+ messageSid: String,
+ mediaSid: String,
+ mediaFileName: String? = null,
+ mediaType: String? = null,
+ mediaSize: Long? = null,
+ mediaUri: String? = null,
+ mediaDownloadId: Long? = null,
+ mediaDownloadedBytes: Long? = null,
+ mediaDownloadState: Int = 0,
+ mediaUploading: Boolean = false,
+ mediaUploadedBytes: Long? = null,
+ mediaUploadUri: String? = null,
+ inputStream: InputStream,
+ messageUuid: String
+ ) :
+ this(
+ messageSid,
+ mediaSid,
+ mediaFileName,
+ mediaType,
+ mediaSize,
+ mediaUri,
+ mediaDownloadId,
+ mediaDownloadedBytes,
+ mediaDownloadState,
+ mediaUploading,
+ mediaUploadedBytes,
+ mediaUploadUri,
+ messageUuid
+ ) {
+ this.inputStream = inputStream
+ }
+}
diff --git a/app/src/main/java/com/twilio/conversations/app/data/localCache/entity/MessageDataItem.kt b/app/src/main/java/com/twilio/conversations/app/data/localCache/entity/MessageDataItem.kt
index f070aae..e682b1e 100644
--- a/app/src/main/java/com/twilio/conversations/app/data/localCache/entity/MessageDataItem.kt
+++ b/app/src/main/java/com/twilio/conversations/app/data/localCache/entity/MessageDataItem.kt
@@ -1,7 +1,5 @@
package com.twilio.conversations.app.data.localCache.entity
-
import androidx.room.Entity
-import androidx.room.PrimaryKey
@Entity(tableName = "message_table", primaryKeys = ["sid", "uuid"])
data class MessageDataItem(
@@ -17,16 +15,7 @@ data class MessageDataItem(
val direction: Int,
val sendStatus: Int,
val uuid: String,
- val mediaSid: String? = null,
- val mediaFileName: String? = null,
- val mediaType: String? = null,
- val mediaSize: Long? = null,
- val mediaUri: String? = null,
- val mediaDownloadId: Long? = null,
- val mediaDownloadedBytes: Long? = null,
- val mediaDownloadState: Int = 0,
- val mediaUploading: Boolean = false,
- val mediaUploadedBytes: Long? = null,
- val mediaUploadUri: String? = null,
val errorCode: Int = 0
)
+
+
diff --git a/app/src/main/java/com/twilio/conversations/app/data/localCache/entity/MessageWithMedia.kt b/app/src/main/java/com/twilio/conversations/app/data/localCache/entity/MessageWithMedia.kt
new file mode 100644
index 0000000..fba0f8c
--- /dev/null
+++ b/app/src/main/java/com/twilio/conversations/app/data/localCache/entity/MessageWithMedia.kt
@@ -0,0 +1,13 @@
+package com.twilio.conversations.app.data.localCache.entity
+
+import androidx.room.Embedded
+import androidx.room.Relation
+
+data class MessageWithMedia(
+ @Embedded val message: MessageDataItem,
+ @Relation(
+ parentColumn = "uuid",
+ entityColumn = "messageUuid"
+ )
+ val media: List?
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/twilio/conversations/app/data/models/MessageListViewItem.kt b/app/src/main/java/com/twilio/conversations/app/data/models/MessageListViewItem.kt
index 0849e66..e9c0ff0 100644
--- a/app/src/main/java/com/twilio/conversations/app/data/models/MessageListViewItem.kt
+++ b/app/src/main/java/com/twilio/conversations/app/data/models/MessageListViewItem.kt
@@ -4,10 +4,23 @@ import android.net.Uri
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
import com.twilio.conversations.app.common.enums.SendStatus
+data class MessageMediaViewItem(
+ val mediaSid: String?,
+ val mediaFileName: String?,
+ val mediaType: String?,
+ val mediaSize: Long?,
+ val mediaUri: Uri?,
+ val mediaDownloadId: Long?,
+ val mediaDownloadedBytes: Long?,
+ val mediaDownloadState: DownloadState,
+ val mediaUploading: Boolean,
+ val mediaUploadedBytes: Long?,
+ val mediaUploadUri: Uri?
+)
+
data class MessageListViewItem(
val sid: String,
val uuid: String,
@@ -21,16 +34,8 @@ data class MessageListViewItem(
val sendStatusIcon: Int,
val reactions: Reactions,
val type: MessageType,
- val mediaSid: String?,
- val mediaFileName: String?,
- val mediaType: String?,
- val mediaSize: Long?,
- val mediaUri: Uri?,
- val mediaDownloadId: Long?,
- val mediaDownloadedBytes: Long?,
- val mediaDownloadState: DownloadState,
- val mediaUploading: Boolean,
- val mediaUploadedBytes: Long?,
- val mediaUploadUri: Uri?,
+ val mediaData: List = emptyList(),
val errorCode: Int
-)
+) {
+ fun hasMedia() = this.mediaData.isNotEmpty()
+}
diff --git a/app/src/main/java/com/twilio/conversations/app/manager/MessageListManager.kt b/app/src/main/java/com/twilio/conversations/app/manager/MessageListManager.kt
index 799537d..38cd8d0 100644
--- a/app/src/main/java/com/twilio/conversations/app/manager/MessageListManager.kt
+++ b/app/src/main/java/com/twilio/conversations/app/manager/MessageListManager.kt
@@ -14,6 +14,7 @@ import com.twilio.conversations.app.common.extensions.firstMedia
import com.twilio.conversations.app.common.extensions.removeMessage
import com.twilio.conversations.app.common.toMessageDataItem
import com.twilio.conversations.app.data.ConversationsClientWrapper
+import com.twilio.conversations.app.data.localCache.entity.MediaDataItem
import com.twilio.conversations.app.data.localCache.entity.MessageDataItem
import com.twilio.conversations.app.data.models.ReactionAttributes
import com.twilio.conversations.app.repository.ConversationsRepository
@@ -30,14 +31,25 @@ import timber.log.Timber
import java.io.InputStream
import java.util.*
+data class MediaInput (
+ val uri: String,
+ val inputStream: InputStream,
+ val fileName: String?,
+ val mimeType: String?
+)
+
interface MessageListManager {
suspend fun sendTextMessage(text: String, uuid: String)
suspend fun retrySendTextMessage(messageUuid: String)
- suspend fun sendMediaMessage(
- uri: String,
- inputStream: InputStream,
- fileName: String?,
- mimeType: String?,
+// suspend fun sendMediaMessage(
+// uri: String,
+// inputStream: InputStream,
+// fileName: String?,
+// mimeType: String?,
+// messageUuid: String
+// )
+ suspend fun sendMultipleMediaMessage(
+ items: List,
messageUuid: String
)
suspend fun retrySendMediaMessage(inputStream: InputStream, messageUuid: String)
@@ -110,73 +122,89 @@ class MessageListManagerImpl(
conversationsRepository.updateMessageByUuid(sentMessage)
}
- override suspend fun sendMediaMessage(
- uri: String,
- inputStream: InputStream,
- fileName: String?,
- mimeType: String?,
- messageUuid: String
- ) {
+ override suspend fun sendMultipleMediaMessage(items: List, messageUuid: String) {
val identity = conversationsClient.getConversationsClient().myIdentity
val conversation = conversationsClient.getConversationsClient().getConversation(conversationSid)
val participantSid = conversation.getParticipantByIdentity(identity).sid
val attributes = Attributes(messageUuid)
- val message = MessageDataItem(
- "",
- conversationSid,
- participantSid,
- MessageType.MEDIA.value,
- identity,
- Date().time,
- null,
- -1,
- attributes.toString(),
- Direction.OUTGOING.value,
- SendStatus.SENDING.value,
- messageUuid,
- mediaFileName = fileName,
- mediaUploadUri = uri,
- mediaType = mimeType
- )
- conversationsRepository.insertMessage(message)
- val sentMessage = conversation.sendMessage {
- this.attributes = attributes
- addMedia(
- inputStream,
- mimeType ?: "",
- fileName,
- createMediaUploadListener(uri, messageUuid)
+ val newMessage =
+ MessageDataItem(
+ "",
+ conversationSid,
+ participantSid,
+ MessageType.MEDIA.value,
+ identity,
+ Date().time,
+ null,
+ -1,
+ attributes.toString(),
+ Direction.OUTGOING.value,
+ SendStatus.SENDING.value,
+ messageUuid
)
- }.toMessageDataItem(identity, messageUuid)
- conversationsRepository.updateMessageByUuid(sentMessage)
+ conversationsRepository.insertMessage(newMessage)
+
+ val mediaList = items.filterNotNull().map {
+ MediaDataItem(
+ "",
+ "",
+ mediaFileName = it.fileName,
+ mediaUploadUri = it.uri,
+ mediaType = it.mimeType,
+ inputStream = it.inputStream,
+ messageUuid = messageUuid
+ )
+ }
+
+ conversationsRepository.insertMedia(mediaList)
+
+ val message = conversation.sendMessage {
+ mediaList.forEach {
+ this.attributes = attributes
+ it.inputStream?.let { it1 ->
+ addMedia(
+ it1,
+ it.mediaType ?: "",
+ it.mediaFileName,
+ createMediaUploadListener(it.mediaUploadUri!!, messageUuid)
+ )
+ }
+ }
+
+ }
+
+ conversationsRepository.updateMessageByUuid(message.toMessageDataItem(identity, messageUuid))
}
override suspend fun retrySendMediaMessage(
inputStream: InputStream,
messageUuid: String
) {
- val message = withContext(dispatchers.io()) { conversationsRepository.getMessageByUuid(messageUuid) } ?: return
- if (message.sendStatus == SendStatus.SENDING.value) return
- if (message.mediaUploadUri == null) {
- Timber.w("Missing mediaUploadUri in retrySendMediaMessage: $message")
+ val messageWithMedia = withContext(dispatchers.io()) { conversationsRepository.getMessageMediaByMessageUuid(messageUuid) } ?: return
+ if (messageWithMedia.message.sendStatus == SendStatus.SENDING.value) return
+ if (messageWithMedia.media?.any{ it.mediaUploadUri == null } == true) {
+ Timber.w("Missing mediaUploadUri in retrySendMediaMessage: ${messageWithMedia.message}")
return
}
- conversationsRepository.updateMessageByUuid(message.copy(sendStatus = SendStatus.SENDING.value))
+ conversationsRepository.updateMessageByUuid(messageWithMedia.message.copy(sendStatus = SendStatus.SENDING.value))
val identity = conversationsClient.getConversationsClient().myIdentity
val conversation = conversationsClient.getConversationsClient().getConversation(conversationSid)
val sentMessage = conversation.sendMessage {
this.attributes = Attributes(messageUuid)
- addMedia(
- inputStream,
- message.mediaType ?: "",
- message.mediaFileName,
- createMediaUploadListener(message.mediaUploadUri, messageUuid)
- )
- }.toMessageDataItem(identity, message.uuid)
+ messageWithMedia.media?.forEach {
+ addMedia(
+ inputStream,
+ it.mediaType ?: "",
+ it.mediaFileName,
+ createMediaUploadListener(it.mediaUploadUri!!, messageUuid)
+ )
+ }
+
+ }.toMessageDataItem(identity, messageWithMedia.message.uuid)
conversationsRepository.updateMessageByUuid(sentMessage)
}
@@ -228,12 +256,15 @@ class MessageListManagerImpl(
downloadedLocation: String?
) {
val message = conversationsClient.getConversationsClient().getConversation(conversationSid).getMessageByIndex(index)
- conversationsRepository.updateMessageMediaDownloadStatus(
- messageSid = message.sid,
- downloadedBytes = downloadedBytes,
- downloadLocation = downloadedLocation,
- downloadState = downloadState.value
- )
+ // @todo fix this logic
+ message.attachedMedia.forEach {
+ conversationsRepository.updateMessageMediaDownloadStatus(
+ mediaSid = it.sid,
+ downloadedBytes = downloadedBytes,
+ downloadLocation = downloadedLocation,
+ downloadState = downloadState.value
+ )
+ }
}
override suspend fun setReactions(index: Long, reactions: Reactions) {
@@ -267,7 +298,10 @@ class MessageListManagerImpl(
override suspend fun setMessageMediaDownloadId(messageIndex: Long, id: Long) {
val message = conversationsClient.getConversationsClient().getConversation(conversationSid).getMessageByIndex(messageIndex)
- conversationsRepository.updateMessageMediaDownloadStatus(messageSid = message.sid, downloadId = id)
+ message.attachedMedia.forEach {
+ conversationsRepository.updateMessageMediaDownloadStatus(it.sid, downloadId = id)
+
+ }
}
override suspend fun removeMessage(messageIndex: Long) {
diff --git a/app/src/main/java/com/twilio/conversations/app/repository/ConversationsRepository.kt b/app/src/main/java/com/twilio/conversations/app/repository/ConversationsRepository.kt
index 6d9d501..96f19e4 100644
--- a/app/src/main/java/com/twilio/conversations/app/repository/ConversationsRepository.kt
+++ b/app/src/main/java/com/twilio/conversations/app/repository/ConversationsRepository.kt
@@ -22,7 +22,9 @@ import com.twilio.conversations.app.common.toMessageDataItem
import com.twilio.conversations.app.data.ConversationsClientWrapper
import com.twilio.conversations.app.data.localCache.LocalCacheProvider
import com.twilio.conversations.app.data.localCache.entity.ConversationDataItem
+import com.twilio.conversations.app.data.localCache.entity.MediaDataItem
import com.twilio.conversations.app.data.localCache.entity.MessageDataItem
+import com.twilio.conversations.app.data.localCache.entity.MessageWithMedia
import com.twilio.conversations.app.data.localCache.entity.ParticipantDataItem
import com.twilio.conversations.app.data.models.MessageListViewItem
import com.twilio.conversations.app.data.models.RepositoryRequestStatus
@@ -64,23 +66,25 @@ interface ConversationsRepository {
fun getConversation(conversationSid: String): Flow>
fun getSelfUser(): Flow
fun getMessageByUuid(messageUuid: String): MessageDataItem?
+ fun getMessageMediaByMessageUuid(messageUuid: String): MessageWithMedia?
// Interim solution till paging v3.0 is available as an alpha version.
// It has support for converting PagedList types
fun getMessages(conversationSid: String, pageSize: Int): Flow>>
fun insertMessage(message: MessageDataItem)
+ fun insertMedia(mediaList: List)
fun updateMessageByUuid(message: MessageDataItem)
fun updateMessageStatus(messageUuid: String, sendStatus: Int, errorCode: Int)
fun getTypingParticipants(conversationSid: String): Flow>
fun getConversationParticipants(conversationSid: String): Flow>>
fun updateMessageMediaDownloadStatus(
- messageSid: String,
+ mediaSid: String,
downloadId: Long? = null,
downloadLocation: String? = null,
downloadState: Int? = null,
downloadedBytes: Long? = null
)
fun updateMessageMediaUploadStatus(
- messageUuid: String,
+ mediaSid: String,
uploading: Boolean? = null,
uploadedBytes: Long? = null
)
@@ -184,6 +188,11 @@ class ConversationsRepositoryImpl(
}
override fun getMessageByUuid(messageUuid: String) = localCache.messagesDao().getMessageByUuid(messageUuid)
+ override fun getMessageMediaByMessageUuid(messageUuid: String): MessageWithMedia? {
+ val media = localCache.mediaDao().getAllMediaByMessageUuid(messageUuid)
+ val message = localCache.messagesDao().getMessageByUuid(messageUuid)
+ return message?.let { MessageWithMedia(it, media) }
+ }
override fun getMessages(conversationSid: String, pageSize: Int): Flow>> {
Timber.v("getMessages($conversationSid, $pageSize)")
@@ -243,6 +252,12 @@ class ConversationsRepositoryImpl(
}
}
+ override fun insertMedia(mediaList: List) {
+ launch {
+ localCache.mediaDao().insert(mediaList)
+ }
+ }
+
override fun updateMessageByUuid(message: MessageDataItem) {
launch {
localCache.messagesDao().updateByUuidOrInsert(message)
@@ -270,7 +285,7 @@ class ConversationsRepositoryImpl(
}
override fun updateMessageMediaDownloadStatus(
- messageSid: String,
+ mediaSid: String,
downloadId: Long?,
downloadLocation: String?,
downloadState: Int?,
@@ -278,31 +293,31 @@ class ConversationsRepositoryImpl(
) {
launch {
if (downloadId != null) {
- localCache.messagesDao().updateMediaDownloadId(messageSid, downloadId)
+ localCache.mediaDao().updateMediaDownloadId(mediaSid, downloadId)
}
if (downloadLocation != null) {
- localCache.messagesDao().updateMediaDownloadLocation(messageSid, downloadLocation)
+ localCache.mediaDao().updateMediaDownloadLocation(mediaSid, downloadLocation)
}
if (downloadState != null) {
- localCache.messagesDao().updateMediaDownloadState(messageSid, downloadState)
+ localCache.mediaDao().updateMediaDownloadState(mediaSid, downloadState)
}
if (downloadedBytes != null) {
- localCache.messagesDao().updateMediaDownloadedBytes(messageSid, downloadedBytes)
+ localCache.mediaDao().updateMediaDownloadedBytes(mediaSid, downloadedBytes)
}
}
}
override fun updateMessageMediaUploadStatus(
- messageUuid: String,
+ mediaSid: String,
uploading: Boolean?,
uploadedBytes: Long?
) {
launch {
if (uploading != null) {
- localCache.messagesDao().updateMediaUploadStatus(messageUuid, uploading)
+ localCache.mediaDao().updateMediaUploadStatus(mediaSid, uploading)
}
if (uploadedBytes != null) {
- localCache.messagesDao().updateMediaUploadedBytes(messageUuid, uploadedBytes)
+ localCache.mediaDao().updateMediaUploadedBytes(mediaSid, uploadedBytes)
}
}
}
diff --git a/app/src/main/java/com/twilio/conversations/app/ui/MessageListActivity.kt b/app/src/main/java/com/twilio/conversations/app/ui/MessageListActivity.kt
index cab6555..8bd55d5 100644
--- a/app/src/main/java/com/twilio/conversations/app/ui/MessageListActivity.kt
+++ b/app/src/main/java/com/twilio/conversations/app/ui/MessageListActivity.kt
@@ -67,9 +67,12 @@ class MessageListActivity : BaseActivity() {
Timber.d("Display send error clicked: ${message.uuid}")
showSendErrorDialog(message)
},
- onDownloadMedia = { message ->
+ onDownloadMedia = { message, media ->
Timber.d("Download clicked: $message")
- messageListViewModel.startMessageMediaDownload(message.index, message.mediaFileName)
+ media.mediaSid?.let {
+ messageListViewModel.startMessageMediaDownload(message.index,
+ it, media.mediaFileName)
+ }
},
onOpenMedia = { uri, mimeType ->
Timber.d("Open clicked")
@@ -207,13 +210,16 @@ class MessageListActivity : BaseActivity() {
if (message.type == MessageType.TEXT) {
messageListViewModel.resendTextMessage(message.uuid)
} else if (message.type == MessageType.MEDIA) {
- val fileInputStream = message.mediaUploadUri?.let { contentResolver.openInputStream(it) }
- if (fileInputStream != null) {
- messageListViewModel.resendMediaMessage(fileInputStream, message.uuid)
- } else {
- Timber.w("Could not get input stream for file reading: ${message.mediaUploadUri}")
- showToast(R.string.err_failed_to_resend_media)
+ message.mediaData?.forEach { media ->
+ val fileInputStream = media.mediaUploadUri?.let { contentResolver.openInputStream(it) }
+ if (fileInputStream != null) {
+ messageListViewModel.resendMediaMessage(fileInputStream, message.uuid)
+ } else {
+ Timber.w("Could not get input stream for file reading: ${media.mediaUploadUri}")
+ showToast(R.string.err_failed_to_resend_media)
+ }
}
+
}
}
diff --git a/app/src/main/java/com/twilio/conversations/app/ui/dialogs/AttachFileDialog.kt b/app/src/main/java/com/twilio/conversations/app/ui/dialogs/AttachFileDialog.kt
index 381315b..43abef6 100644
--- a/app/src/main/java/com/twilio/conversations/app/ui/dialogs/AttachFileDialog.kt
+++ b/app/src/main/java/com/twilio/conversations/app/ui/dialogs/AttachFileDialog.kt
@@ -8,7 +8,7 @@ import android.provider.OpenableColumns
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import androidx.activity.result.contract.ActivityResultContracts.OpenDocument
+import androidx.activity.result.contract.ActivityResultContracts.OpenMultipleDocuments
import androidx.activity.result.contract.ActivityResultContracts.TakePicture
import androidx.core.content.FileProvider
import com.twilio.conversations.app.common.enums.ConversationsError
@@ -18,6 +18,7 @@ import com.twilio.conversations.app.common.extensions.lazyActivityViewModel
import com.twilio.conversations.app.common.extensions.parcelable
import com.twilio.conversations.app.common.injector
import com.twilio.conversations.app.databinding.DialogAttachFileBinding
+import com.twilio.conversations.app.manager.MediaInput
import timber.log.Timber
import java.io.File
import java.text.SimpleDateFormat
@@ -39,8 +40,10 @@ class AttachFileDialog : BaseBottomSheetDialogFragment() {
dismiss()
}
- private val openDocument = registerForActivityResult(OpenDocument()) { uri: Uri? ->
- uri?.let { sendMediaMessage(it) }
+ private val openMultipleDocuments = registerForActivityResult(OpenMultipleDocuments()) { uriList: List? ->
+ if (uriList != null) {
+ sendMultipleMediaMessage(uriList)
+ }
dismiss()
}
@@ -66,7 +69,7 @@ class AttachFileDialog : BaseBottomSheetDialogFragment() {
}
binding.fileManager.setOnClickListener {
- openDocument.launch(arrayOf("*/*"))
+ openMultipleDocuments.launch(arrayOf("*/*"))
}
}
@@ -92,13 +95,39 @@ class AttachFileDialog : BaseBottomSheetDialogFragment() {
val type = contentResolver.getType(uri)
val name = contentResolver.getString(uri, OpenableColumns.DISPLAY_NAME)
if (inputStream != null) {
- messageListViewModel.sendMediaMessage(uri.toString(), inputStream, name, type)
+ messageListViewModel.sendMultipleMediaMessage(listOf(MediaInput(uri.toString(), inputStream, name, type)))
} else {
messageListViewModel.onMessageError.value = ConversationsError.MESSAGE_SEND_FAILED
Timber.w("Could not get input stream for file reading: $uri")
}
}
+ fun sendMultipleMediaMessage(uriList: List) {
+ var failed = false
+ val contentResolver = requireContext().contentResolver
+ val mediaInput = uriList.map { uri ->
+ val inputStream = contentResolver.openInputStream(uri)
+ val type = contentResolver.getType(uri)
+ val name = contentResolver.getString(uri, OpenableColumns.DISPLAY_NAME)
+ if (inputStream == null) {
+ failed = true;
+ Timber.w("Could not get input stream for file reading: $uri")
+ null
+ } else {
+ MediaInput(uri.toString(), inputStream, name, type)
+ }
+ }
+ if (failed) {
+ messageListViewModel.onMessageError.value = ConversationsError.MESSAGE_SEND_FAILED
+
+ }
+
+ messageListViewModel.sendMultipleMediaMessage(mediaInput)
+
+
+
+ }
+
companion object {
diff --git a/app/src/main/java/com/twilio/conversations/app/ui/dialogs/MessageActionsDialog.kt b/app/src/main/java/com/twilio/conversations/app/ui/dialogs/MessageActionsDialog.kt
index 27abbf3..b12aa51 100644
--- a/app/src/main/java/com/twilio/conversations/app/ui/dialogs/MessageActionsDialog.kt
+++ b/app/src/main/java/com/twilio/conversations/app/ui/dialogs/MessageActionsDialog.kt
@@ -67,15 +67,15 @@ class MessageActionsDialog : BaseBottomSheetDialogFragment() {
private fun shareMessage(message: MessageListViewItem) {
val intent = Intent(Intent.ACTION_SEND)
- if (message.type == MEDIA) {
- intent.type = message.mediaType
- val uri = message.mediaUploadUri ?: message.mediaUri ?: return
- intent.putExtra(Intent.EXTRA_STREAM, uri)
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
- } else {
- intent.type = "text/plain"
- intent.putExtra(Intent.EXTRA_TEXT, message.body)
- }
+// if (message.type == MEDIA) {
+// intent.type = message.mediaType
+// val uri = message.mediaUploadUri ?: message.mediaUri ?: return
+// intent.putExtra(Intent.EXTRA_STREAM, uri)
+// intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+// } else {
+// intent.type = "text/plain"
+// intent.putExtra(Intent.EXTRA_TEXT, message.body)
+// }
startActivity(Intent.createChooser(intent, null))
}
diff --git a/app/src/main/java/com/twilio/conversations/app/viewModel/MessageListViewModel.kt b/app/src/main/java/com/twilio/conversations/app/viewModel/MessageListViewModel.kt
index 0d3fd9d..4d8d303 100644
--- a/app/src/main/java/com/twilio/conversations/app/viewModel/MessageListViewModel.kt
+++ b/app/src/main/java/com/twilio/conversations/app/viewModel/MessageListViewModel.kt
@@ -34,6 +34,7 @@ import com.twilio.conversations.app.common.extensions.queryById
import com.twilio.conversations.app.data.localCache.entity.ParticipantDataItem
import com.twilio.conversations.app.data.models.MessageListViewItem
import com.twilio.conversations.app.data.models.RepositoryRequestStatus
+import com.twilio.conversations.app.manager.MediaInput
import com.twilio.conversations.app.manager.MessageListManager
import com.twilio.conversations.app.repository.ConversationsRepository
import com.twilio.util.TwilioException
@@ -89,9 +90,11 @@ class MessageListViewModel(
private val messagesObserver: Observer> =
Observer { list ->
list.forEach { message ->
- if (message?.mediaDownloadState == DownloadState.DOWNLOADING && message.mediaDownloadId != null) {
- if (updateMessageMediaDownloadState(message.index, message.mediaDownloadId)) {
- observeMessageMediaDownload(message.index, message.mediaDownloadId)
+ message.mediaData.forEach { media ->
+ if (media.mediaDownloadState == DownloadState.DOWNLOADING && media.mediaDownloadId != null) {
+ if (updateMessageMediaDownloadState(message.index, media.mediaDownloadId)) {
+ observeMessageMediaDownload(message.index, media.mediaDownloadId)
+ }
}
}
}
@@ -144,11 +147,25 @@ class MessageListViewModel(
}
}
- fun sendMediaMessage(uri: String, inputStream: InputStream, fileName: String?, mimeType: String?) =
+// fun sendMediaMessage(uri: String, inputStream: InputStream, fileName: String?, mimeType: String?) =
+// viewModelScope.launch {
+// val messageUuid = UUID.randomUUID().toString()
+// try {
+// messageListManager.sendMediaMessage(uri, inputStream, fileName, mimeType, messageUuid)
+// onMessageSent.call()
+// Timber.d("Media message sent: $messageUuid")
+// } catch (e: TwilioException) {
+// Timber.d("Media message send error: ${e.errorInfo.status}:${e.errorInfo.code} ${e.errorInfo.message}")
+// messageListManager.updateMessageStatus(messageUuid, SendStatus.ERROR, e.errorInfo.code)
+// onMessageError.value = ConversationsError.MESSAGE_SEND_FAILED
+// }
+// }
+
+ fun sendMultipleMediaMessage(items: List) =
viewModelScope.launch {
val messageUuid = UUID.randomUUID().toString()
try {
- messageListManager.sendMediaMessage(uri, inputStream, fileName, mimeType, messageUuid)
+ messageListManager.sendMultipleMediaMessage(items, messageUuid)
onMessageSent.call()
Timber.d("Media message sent: $messageUuid")
} catch (e: TwilioException) {
@@ -227,7 +244,7 @@ class MessageListViewModel(
)
}
- fun startMessageMediaDownload(messageIndex: Long, fileName: String?) = viewModelScope.launch {
+ fun startMessageMediaDownload(messageIndex: Long, mediaSid: String, fileName: String?) = viewModelScope.launch {
Timber.d("Start file download for message index $messageIndex")
updateMessageMediaDownloadStatus(messageIndex, DownloadState.DOWNLOADING)
diff --git a/app/src/main/res/layout/row_message_item_incoming.xml b/app/src/main/res/layout/row_message_item_incoming.xml
index f17c764..4742796 100644
--- a/app/src/main/res/layout/row_message_item_incoming.xml
+++ b/app/src/main/res/layout/row_message_item_incoming.xml
@@ -12,6 +12,8 @@
+
+
@@ -22,168 +24,117 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
- android:focusableInTouchMode="false"
- tools:background="@color/white">
+ android:focusableInTouchMode="false">
-
+ tools:visibility="gone" />
+ app:layout_constraintTop_toBottomOf="@id/message_header"
+ tools:visibility="visible" />
+ app:layout_constraintStart_toStartOf="@+id/message_background_barrier"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_goneMarginStart="8dp" />
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:orientation="vertical"
+ android:visibility="gone"
+ app:layout_constraintBottom_toTopOf="@id/message_date"
+ app:layout_constraintEnd_toEndOf="@id/message_body"
+ app:layout_constraintStart_toStartOf="@id/message_body"
+ app:layout_constraintTop_toBottomOf="@id/message_body"
+ tools:visibility="gone" />
+
+
+
+
+
@@ -217,15 +180,11 @@
android:id="@+id/attachment_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:visibility="@{message.type == MessageType.MEDIA ? View.VISIBLE : View.GONE}"
- app:constraint_referenced_ids="
- attachment_info,
- attachment_icon,
- attachment_file_name,
- attachment_background,
- attachment_footer"
+ app:constraint_referenced_ids="attachments_container"
tools:visibility="visible" />
+
+
diff --git a/app/src/main/res/layout/row_message_item_outgoing.xml b/app/src/main/res/layout/row_message_item_outgoing.xml
index 21c8942..f5104fb 100644
--- a/app/src/main/res/layout/row_message_item_outgoing.xml
+++ b/app/src/main/res/layout/row_message_item_outgoing.xml
@@ -50,12 +50,14 @@
android:id="@+id/message_background"
android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginTop="4dp"
+ android:layout_marginStart="16dp"
android:background="@drawable/bg_message_outgoing"
app:layout_constraintBottom_toBottomOf="@+id/message_footer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/message_background_barrier"
- app:layout_constraintTop_toTopOf="@+id/message_avatar" />
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_goneMarginStart="8dp" />
+
+ attachments_container" />
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -225,12 +151,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
- android:visibility="@{message.sendStatus == SendStatus.ERROR ? View.VISIBLE : View.GONE}"
android:text="@string/message_send_error"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintHorizontal_bias="1"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toBottomOf="@+id/message_background" />
+ android:visibility="@{message.sendStatus == SendStatus.ERROR ? View.VISIBLE : View.GONE}"
+ app:layout_constraintEnd_toEndOf="@+id/message_background"
+ app:layout_constraintTop_toBottomOf="@+id/message_background"
+ tools:visibility="visible" />
+
+ app:constraint_referenced_ids="attachments_container"
+ tools:visibility="visible" />
+
+
diff --git a/app/src/main/res/layout/row_message_media_item.xml b/app/src/main/res/layout/row_message_media_item.xml
new file mode 100644
index 0000000..13e9eab
--- /dev/null
+++ b/app/src/main/res/layout/row_message_media_item.xml
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index 028849c..6dbd767 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,10 +1,10 @@
wrapper {
- gradleVersion = '8.1.1'
+ gradleVersion = '8.5'
distributionType = Wrapper.DistributionType.ALL
}
buildscript {
- ext.kotlin_version = '1.9.20'
+ ext.kotlin_version = '1.9.21'
ext.kotlinx_coroutines_version = '1.7.3'
ext.mockk_version = '1.12.2'
ext.paging_version = '2.1.2'
@@ -16,7 +16,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.1.4'
+ classpath 'com.android.tools.build:gradle:8.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.4.0'
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
@@ -27,9 +27,13 @@ allprojects {
repositories {
google()
mavenCentral()
+ flatDir {
+ dirs 'aars'
+ }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
+
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 8707e8b..a595206 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-all.zip
-networkTimeout=10000
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists