diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index bced7e303d..6fc6e89a22 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -48,7 +48,7 @@ awaitility = "4.1.0"
branch = "5.1.1"
cashfree = "2.0.6"
chucker = "4.0.0"
-coilCompose = "2.4.0"
+coil = "2.4.0"
compose-bom = "2023.09.01"
compose-lib = '1.5.3'
delight-advancedWebView = "v3.0.0"
@@ -226,8 +226,8 @@ cashfree = { module = "com.cashfree.pg:api", version.ref = "cashfree" }
chucker-library = { module = "com.github.chuckerteam.chucker:library", version.ref = "chucker" }
chucker-libraryNoOp = { module = "com.github.chuckerteam.chucker:library-no-op", version.ref = "chucker" }
-coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
-
+coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coil" }
+coil-video = { module = "io.coil-kt:coil-video", version.ref = "coil" }
dagger-hiltAndroid = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
dagger-hiltAndroidCompiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
dagger-hiltAndroidTesting = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" }
diff --git a/navi-chat/build.gradle b/navi-chat/build.gradle
index f26ca0df6d..16de97cbdf 100644
--- a/navi-chat/build.gradle
+++ b/navi-chat/build.gradle
@@ -55,6 +55,8 @@ dependencies {
implementation libs.android.material
+ implementation libs.coil.video
+
implementation libs.dagger.hiltAndroid
kapt libs.dagger.hiltCompiler
diff --git a/navi-chat/src/main/AndroidManifest.xml b/navi-chat/src/main/AndroidManifest.xml
index 98060b9ea9..97f1724bf5 100644
--- a/navi-chat/src/main/AndroidManifest.xml
+++ b/navi-chat/src/main/AndroidManifest.xml
@@ -48,6 +48,11 @@
android:exported="false"
android:screenOrientation="portrait"
android:theme="@style/BaseThemeStyle" />
+
\ No newline at end of file
diff --git a/navi-chat/src/main/java/com/navi/chat/db/utils/ChatUtils.kt b/navi-chat/src/main/java/com/navi/chat/db/utils/ChatUtils.kt
index 092c0c33f3..c867a65a9d 100644
--- a/navi-chat/src/main/java/com/navi/chat/db/utils/ChatUtils.kt
+++ b/navi-chat/src/main/java/com/navi/chat/db/utils/ChatUtils.kt
@@ -7,10 +7,21 @@
package com.navi.chat.db.utils
+import android.annotation.SuppressLint
+import android.content.ContentResolver
+import android.content.Context
+import android.database.Cursor
+import android.net.Uri
+import android.provider.OpenableColumns
+import android.webkit.MimeTypeMap
import com.google.gson.Gson
+import com.navi.chat.utils.TIME_FORMAT_TO_DISPLAY
import com.navi.chat.utils.USER_SENDER_TYPE
+import com.navi.chat.utils.maxFileSizeInKb
+import com.navi.common.utils.log
import com.navi.naviwidgets.models.NaviChatWidget
import com.navi.naviwidgets.models.response.*
+import java.io.File
fun transformFirestoreToWidgetModel(data: Map): NaviChatWidget =
when (data["widget_name"]) {
@@ -105,4 +116,92 @@ fun getTimerExpiredResponse(timerExpired: Boolean, senderName: String) =
senderName = senderName
),
shouldDisplayInChatHistory = false
- )
\ No newline at end of file
+ )
+
+fun getMimeType(context: Context, uri: Uri): String? {
+ return context.contentResolver.getType(uri)
+}
+
+fun timeInString(seconds: Int): String {
+ return String.format(
+ TIME_FORMAT_TO_DISPLAY, (seconds / 3600 * 60 + ((seconds % 3600) / 60)), (seconds % 60)
+ )
+}
+
+@SuppressLint("Range")
+fun getFileName(contentResolver: ContentResolver, uri: Uri): String {
+ val uriString = uri.toString()
+ var displayName = ""
+ if (uriString.startsWith("content://")) {
+ var cursor: Cursor? = null
+ try {
+ cursor = contentResolver.query(uri, null, null, null, null)
+ if (cursor != null && cursor.moveToFirst()) {
+ displayName = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
+ }
+ } finally {
+ cursor?.close()
+ }
+ } else if (uriString.startsWith("file://")) {
+ displayName = File(uriString).name
+ }
+ return displayName
+}
+
+fun isFileTooLarge(
+ contentResolver: ContentResolver,
+ uri: Uri,
+ fileExtension: String?,
+ maxFileSizeMap: Map?
+): Boolean {
+ val fileSizeInKb = getFileSizeInBytes(contentResolver, uri)
+ val maxSize = fileExtension?.let { maxFileSizeMap?.get(it.uppercase()) } ?: maxFileSizeInKb.toLong()
+ return fileSizeInKb > maxSize * 1024
+}
+
+private fun getFileSizeInBytes(contentResolver: ContentResolver, uri: Uri): Long {
+ var fileSize: Long = 0
+
+ if (uri.scheme == "content") {
+ val cursor = contentResolver.query(uri, null, null, null, null)
+ cursor?.use {
+ if (it.moveToFirst()) {
+ val sizeIndex = it.getColumnIndex(OpenableColumns.SIZE)
+ if (sizeIndex != -1) {
+ fileSize = it.getLong(sizeIndex)
+ }
+ }
+ }
+ } else if (uri.scheme == "file") {
+ try {
+ val file = uri.path?.let { File(it) }
+ if (file != null) {
+ if (file.exists()) {
+ fileSize = file.length()
+ }
+ }
+ } catch (e: Exception) {
+ e.log()
+ }
+ }
+
+ return fileSize
+}
+
+fun stringToMap(data: String): Map {
+ val keyValuePairs = data.removeSurrounding("{", "}").split(", ").map { it.split("=") }
+
+ return keyValuePairs.associate { (key, value) -> key to value.toLong() }
+}
+
+fun fileSizeWithinLimit(
+ context: Context,
+ selectedUri: Uri,
+ contentResolver: ContentResolver,
+ fileSizesMap: Map
+): Boolean {
+ val mimeType = getMimeType(context, Uri.parse(selectedUri.toString()))
+ val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType)?.lowercase()
+ if (!isFileTooLarge(contentResolver, selectedUri, extension, fileSizesMap)) return true
+ return false
+}
diff --git a/navi-chat/src/main/java/com/navi/chat/interfaces/AttachmentOptionClickListener.kt b/navi-chat/src/main/java/com/navi/chat/interfaces/AttachmentOptionClickListener.kt
new file mode 100644
index 0000000000..94006c764e
--- /dev/null
+++ b/navi-chat/src/main/java/com/navi/chat/interfaces/AttachmentOptionClickListener.kt
@@ -0,0 +1,13 @@
+/*
+ *
+ * * Copyright © 2023 by Navi Technologies Limited
+ * * All rights reserved. Strictly confidential
+ *
+ */
+
+package com.navi.chat.interfaces
+
+interface AttachmentOptionClickListener {
+ fun onGalleryAttachmentOptionClicked()
+ fun onFileAttachmentOptionClicked()
+}
diff --git a/navi-chat/src/main/java/com/navi/chat/ui/activities/AttachmentConfirmationActivity.kt b/navi-chat/src/main/java/com/navi/chat/ui/activities/AttachmentConfirmationActivity.kt
new file mode 100644
index 0000000000..c4aceff248
--- /dev/null
+++ b/navi-chat/src/main/java/com/navi/chat/ui/activities/AttachmentConfirmationActivity.kt
@@ -0,0 +1,69 @@
+package com.navi.chat.ui.activities
+
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import com.navi.chat.db.utils.getFileName
+import com.navi.chat.ui.compose.AttachmentPickerScreen
+import com.navi.chat.utils.FILENAME
+import com.navi.chat.utils.FILE_SIZES
+import com.navi.chat.utils.FILE_TYPE_PARAM
+import com.navi.chat.utils.FILE_URI
+import com.navi.chat.utils.NAVI_CHAT_ATTACHMENT_CONFIRMATION_ACTIVITY
+import com.navi.chat.utils.URI_LIST
+import com.navi.common.model.ModuleNameV2
+import com.navi.common.ui.activity.BaseActivity
+
+class AttachmentConfirmationActivity : BaseActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val data = intent.getStringExtra(FILE_TYPE_PARAM)
+ val fileSizes = intent.getStringExtra(FILE_SIZES)
+ setContent {
+ AttachmentPickerScreen(
+ onBackPress = { onBackPressed() },
+ onSendClick = { selectedImages ->
+ val intent = createMyIntent(selectedImages)
+ startActivity(intent)
+ },
+ onPlayClick = { fileUri ->
+ val intent = Intent(this, NaviChatViewVideoActivity::class.java)
+ val bundle = Bundle()
+ bundle.putString(
+ FILENAME, getFileName(this.contentResolver, fileUri)
+ )
+ intent.putExtra(FILE_URI, fileUri.toString())
+ intent.putExtras(bundle)
+ this.startActivity(intent)
+ },
+ onImageClick = { fileUri ->
+ val intent = Intent(this, NaviChatViewImageActivity::class.java)
+ val bundle = Bundle()
+ bundle.putString(
+ FILENAME, getFileName(this.contentResolver, fileUri)
+ )
+ intent.putExtra(FILE_URI, fileUri.toString())
+ intent.putExtras(bundle)
+ this.startActivity(intent)
+ },
+ data,
+ fileSizes.toString()
+ )
+ }
+ }
+
+ private fun createMyIntent(selectedImages: List): Intent {
+ val intent = Intent(this, NaviChatActivity::class.java)
+ intent.putParcelableArrayListExtra(URI_LIST, ArrayList(selectedImages))
+ setResult(RESULT_OK, intent)
+ finish()
+ return intent
+ }
+
+ override val screenName: String
+ get() = NAVI_CHAT_ATTACHMENT_CONFIRMATION_ACTIVITY
+ override val moduleName: ModuleNameV2
+ get() = ModuleNameV2.CHAT
+}
diff --git a/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatActivity.kt b/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatActivity.kt
index d4925bfd9c..8d40b6c886 100644
--- a/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatActivity.kt
+++ b/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatActivity.kt
@@ -7,22 +7,49 @@
package com.navi.chat.ui.activities
+import android.app.Activity
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
+import androidx.activity.result.contract.ActivityResultContracts
import androidx.databinding.DataBindingUtil
+import androidx.lifecycle.ViewModelProvider
import com.navi.chat.R
import com.navi.chat.base.ChatBaseActivity
import com.navi.chat.databinding.ActivityNaviChatBinding
+import com.navi.chat.db.utils.getFileName
+import com.navi.chat.interfaces.AttachmentOptionClickListener
import com.navi.chat.models.NaviChatSystemLocalData
import com.navi.chat.ui.fragments.NaviChatFragment
import com.navi.chat.utils.CONVERSATION_ID_PARAM
+import com.navi.chat.utils.DOCUMENT_ITEM
+import com.navi.chat.utils.FILE_SIZES
+import com.navi.chat.utils.FILE_TYPE_PARAM
+import com.navi.chat.utils.GALLERY_ITEM
import com.navi.chat.utils.NAVI_CHAT_SYSTEM_LOCAL_DATA
+import com.navi.chat.utils.URI_LIST
+import com.navi.chat.viewmodels.AttachmentPickerViewModel
-class NaviChatActivity : ChatBaseActivity() {
+class NaviChatActivity : ChatBaseActivity() , AttachmentOptionClickListener{
private lateinit var binding: ActivityNaviChatBinding
private val currentScreenTag: String = NaviChatFragment.TAG
private var chatFragment: NaviChatFragment? = null
+ private val attachmentPickerViewModel by lazy {
+ ViewModelProvider(this)[AttachmentPickerViewModel::class.java]
+ }
+ private var resultLauncher =
+ registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == Activity.RESULT_OK) {
+ val data = result.data
+ val receivedDataList: List? = data?.getParcelableArrayListExtra(URI_LIST)
+ receivedDataList?.forEach { receivedData ->
+ attachmentPickerViewModel.setImagePickerData(
+ receivedData.toString(), getFileName(contentResolver, receivedData)
+ )
+ }
+ }
+ }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -88,4 +115,17 @@ class NaviChatActivity : ChatBaseActivity() {
var isChatActivityVisible: Boolean = false
private set
}
+
+ override fun onGalleryAttachmentOptionClicked() {
+ val intent = Intent(this, AttachmentConfirmationActivity::class.java)
+ intent.putExtra(FILE_TYPE_PARAM, GALLERY_ITEM)
+ intent.putExtra(FILE_SIZES, attachmentPickerViewModel.getFileSizes().toString())
+ resultLauncher.launch(intent)
+ }
+ override fun onFileAttachmentOptionClicked() {
+ val intent = Intent(this, AttachmentConfirmationActivity::class.java)
+ intent.putExtra(FILE_TYPE_PARAM, DOCUMENT_ITEM)
+ intent.putExtra(FILE_SIZES, attachmentPickerViewModel.getFileSizes().toString())
+ resultLauncher.launch(intent)
+ }
}
diff --git a/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatViewImageActivity.kt b/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatViewImageActivity.kt
index f30162ac58..0124bb0e81 100644
--- a/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatViewImageActivity.kt
+++ b/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatViewImageActivity.kt
@@ -10,6 +10,7 @@ package com.navi.chat.ui.activities
import android.os.Bundle
import android.widget.Toast
import androidx.core.content.FileProvider
+import androidx.core.net.toUri
import androidx.databinding.DataBindingUtil
import com.bumptech.glide.Glide
import com.navi.chat.R
@@ -20,6 +21,7 @@ import com.navi.chat.di.dependencies.NaviChatModuleDependencies
import com.navi.chat.di.modules.NaviChatModule
import com.navi.chat.utils.ChatFileHelper
import com.navi.chat.utils.FILENAME
+import com.navi.chat.utils.FILE_URI
import com.navi.chat.utils.ToolbarInteraction
import dagger.hilt.android.EntryPointAccessors
import java.io.File
@@ -71,8 +73,11 @@ class NaviChatViewImageActivity : ChatBaseActivity(), ToolbarInteraction {
val sd: File = this.cacheDir
val folder = File(sd, "/chat/")
val outputFile = File(folder, fileName)
- val uri =
+ val uri = if (outputFile.exists()) {
FileProvider.getUriForFile(this, "${this.packageName}.fileprovider", outputFile)
+ } else {
+ intent.getStringExtra(FILE_URI)?.toUri()
+ }
Glide.with(this).load(uri).into(binding.ivImageAttachment)
} else {
Toast.makeText(this, getString(R.string.file_cannot_be_opened), Toast.LENGTH_LONG)
diff --git a/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatViewVideoActivity.kt b/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatViewVideoActivity.kt
index a49f8f0d5e..df53a65cc0 100644
--- a/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatViewVideoActivity.kt
+++ b/navi-chat/src/main/java/com/navi/chat/ui/activities/NaviChatViewVideoActivity.kt
@@ -19,6 +19,7 @@ import android.view.*
import android.widget.SeekBar
import android.widget.Toast
import androidx.core.content.FileProvider
+import androidx.core.net.toUri
import androidx.core.view.isVisible
import androidx.databinding.DataBindingUtil
import com.navi.chat.R
@@ -228,10 +229,15 @@ class NaviChatViewVideoActivity :
val sd: File = this.cacheDir
val folder = File(sd, "/chat/")
val outputFile = File(folder, it)
- val uri =
+ val uri = if (outputFile.exists()) {
FileProvider.getUriForFile(this, "${this.packageName}.fileprovider", outputFile)
+ } else {
+ intent.getStringExtra(FILE_URI)?.toUri()
+ }
mediaPlayer?.apply {
- setDataSource(applicationContext, uri)
+ if (uri != null) {
+ setDataSource(applicationContext, uri)
+ }
}
mediaPlayer?.setOnPreparedListener(this)
mediaPlayer?.setOnCompletionListener(this)
@@ -304,12 +310,12 @@ class NaviChatViewVideoActivity :
private fun pauseMediaPlayer() {
mediaPlayer?.pause()
- binding.ivPlayPauseVideo.setImageResource(R.drawable.ic_play_video)
+ binding.ivPlayPauseVideo.setImageResource(R.drawable.ic_play_video_purple)
}
private fun startMediaPlayer() {
mediaPlayer?.start()
- binding.ivPlayPauseVideo.setImageResource(R.drawable.ic_pause_icon)
+ binding.ivPlayPauseVideo.setImageResource(R.drawable.ic_pause_icon_purple)
}
@SuppressLint("ClickableViewAccessibility")
@@ -352,7 +358,7 @@ class NaviChatViewVideoActivity :
// Go to start when video completes
override fun onCompletion(mp: MediaPlayer?) {
setVideoPlayerControlsVisibility(true)
- binding.ivPlayPauseVideo.setImageResource(R.drawable.ic_play_video)
+ binding.ivPlayPauseVideo.setImageResource(R.drawable.ic_play_video_purple)
mediaPlayerSeekTo(0)
}
diff --git a/navi-chat/src/main/java/com/navi/chat/ui/components/FileDocument.kt b/navi-chat/src/main/java/com/navi/chat/ui/components/FileDocument.kt
new file mode 100644
index 0000000000..f33b80eaba
--- /dev/null
+++ b/navi-chat/src/main/java/com/navi/chat/ui/components/FileDocument.kt
@@ -0,0 +1,34 @@
+package com.navi.chat.ui.components
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.annotation.CallSuper
+import com.navi.chat.utils.MIME_TYPE_DOCX
+import com.navi.chat.utils.MIME_TYPE_DOC
+import com.navi.chat.utils.MIME_TYPE_PDF
+
+class FileDocument : ActivityResultContract, Uri?>() {
+ @CallSuper
+ override fun createIntent(context: Context, input: Array): Intent {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ intent.type = MIME_TYPE_PDF
+ intent.putExtra(
+ Intent.EXTRA_MIME_TYPES, arrayOf(
+ MIME_TYPE_PDF, MIME_TYPE_DOC, MIME_TYPE_DOCX
+ )
+ )
+ return intent
+ }
+
+ override fun getSynchronousResult(
+ context: Context, input: Array
+ ): SynchronousResult? = null
+
+ override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
+ return intent.takeIf { resultCode == Activity.RESULT_OK }?.data
+ }
+}
\ No newline at end of file
diff --git a/navi-chat/src/main/java/com/navi/chat/ui/components/GalleryDocument.kt b/navi-chat/src/main/java/com/navi/chat/ui/components/GalleryDocument.kt
new file mode 100644
index 0000000000..d3c63e7017
--- /dev/null
+++ b/navi-chat/src/main/java/com/navi/chat/ui/components/GalleryDocument.kt
@@ -0,0 +1,29 @@
+package com.navi.chat.ui.components
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.net.Uri
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.annotation.CallSuper
+import com.navi.chat.utils.IMAGE_TYPE
+import com.navi.chat.utils.VIDEO_TYPE
+
+class GalleryDocument : ActivityResultContract, Uri?>() {
+ @CallSuper
+ override fun createIntent(context: Context, input: Array): Intent {
+ val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ intent.type = IMAGE_TYPE
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf(IMAGE_TYPE, VIDEO_TYPE))
+ return intent
+ }
+
+ override fun getSynchronousResult(
+ context: Context, input: Array
+ ): SynchronousResult? = null
+
+ override fun parseResult(resultCode: Int, intent: Intent?): Uri? {
+ return intent.takeIf { resultCode == Activity.RESULT_OK }?.data
+ }
+}
\ No newline at end of file
diff --git a/navi-chat/src/main/java/com/navi/chat/ui/compose/AttachmentPickerScreen.kt b/navi-chat/src/main/java/com/navi/chat/ui/compose/AttachmentPickerScreen.kt
new file mode 100644
index 0000000000..fdbe570134
--- /dev/null
+++ b/navi-chat/src/main/java/com/navi/chat/ui/compose/AttachmentPickerScreen.kt
@@ -0,0 +1,417 @@
+package com.navi.chat.ui.compose
+
+import android.annotation.SuppressLint
+import android.content.Intent
+import android.media.MediaMetadataRetriever
+import android.net.Uri
+import android.widget.Toast
+import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import coil.ImageLoader
+import coil.compose.AsyncImage
+import coil.compose.rememberAsyncImagePainter
+import coil.decode.VideoFrameDecoder
+import coil.request.ImageRequest
+import com.navi.chat.R
+import com.navi.chat.db.utils.fileSizeWithinLimit
+import com.navi.chat.db.utils.getFileName
+import com.navi.chat.db.utils.getMimeType
+import com.navi.chat.db.utils.stringToMap
+import com.navi.chat.db.utils.timeInString
+import com.navi.chat.ui.components.FileDocument
+import com.navi.chat.ui.components.GalleryDocument
+import com.navi.chat.ui.theme.NaviChatColor
+import com.navi.chat.utils.MIME_TYPE_DOCX
+import com.navi.chat.utils.MIME_TYPE_DOC
+import com.navi.chat.utils.GALLERY_ITEM
+import com.navi.chat.utils.IMAGE_TYPE
+import com.navi.chat.utils.MIME_TYPE_IMAGE
+import com.navi.chat.utils.MIME_TYPE_PDF
+import com.navi.chat.utils.MIME_TYPE_VIDEO
+import com.navi.chat.utils.VIDEO_TYPE
+import com.navi.chat.utils.maxFileLimit
+import com.navi.design.theme.composeFontFamily
+import com.navi.design.theme.ttComposeFontFamily
+import kotlinx.coroutines.launch
+
+@Composable
+fun AttachmentPickerScreen(
+ onBackPress: () -> Unit = {},
+ onSendClick: (selectedImages: MutableList) -> Unit,
+ onPlayClick: (fileUri: Uri) -> Unit,
+ onImageClick: (fileUri: Uri) -> Unit,
+ data: String?,
+ fileSizes: String,
+) {
+ val selectedImages = remember { mutableStateListOf() }
+ val primaryButtonEnabled = remember { mutableStateOf(false) }
+
+ Scaffold(modifier = Modifier.fillMaxSize(), topBar = {
+ NaviChatHeader(
+ title = stringResource(id = R.string.navi_chat_toolbar_title),
+ onNavigationIconClick = { onBackPress() },
+ modifier = Modifier
+ )
+ }, bottomBar = {
+ NaviChatFooter(
+ secondaryButtonText = stringResource(id = R.string.cancel),
+ primaryButtonText = stringResource(id = R.string.send),
+ onSecondaryButtonClicked = { onBackPress() },
+ onPrimaryButtonClicked = { onSendClick(selectedImages) },
+ primaryButtonEnabled = primaryButtonEnabled.value
+ )
+ }, content = { paddingValues ->
+ NaviChatAttachmentScreenBody(
+ selectedImages,
+ data,
+ paddingValues,
+ fileSizes,
+ onPlayClick = { uri -> onPlayClick(uri) },
+ onImageClick = { uri -> onImageClick(uri) },
+ primaryButtonEnabled = primaryButtonEnabled
+ )
+ })
+}
+
+@Composable
+fun NaviChatAttachmentScreenBody(
+ selectedImages: MutableList,
+ data: String?,
+ padding: PaddingValues,
+ fileSizes: String,
+ onPlayClick: (fileUri: Uri) -> Unit,
+ onImageClick: (fileUri: Uri) -> Unit,
+ primaryButtonEnabled: MutableState
+) {
+ val context = LocalContext.current
+ val contentResolver = context.contentResolver
+ val fileSizesMap = stringToMap(fileSizes)
+
+ primaryButtonEnabled.value = selectedImages.size != 0
+ val photoPickerLauncher = rememberLauncherForActivityResult(
+ contract = GalleryDocument()
+ ) { selectedUri: Uri? ->
+ if (selectedUri != null) {
+ if (fileSizeWithinLimit(context, selectedUri, contentResolver, fileSizesMap)) {
+ selectedImages.add(selectedUri)
+ } else Toast.makeText(
+ context, R.string.file_size_exceeded, Toast.LENGTH_SHORT
+ ).show()
+ val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ contentResolver?.takePersistableUriPermission(selectedUri, takeFlags)
+ }
+ }
+
+ val documentPickerLauncher = rememberLauncherForActivityResult(
+ contract = FileDocument()
+ ) { selectedUri: Uri? ->
+ if (selectedUri != null) {
+ if (fileSizeWithinLimit(context, selectedUri, contentResolver, fileSizesMap)) {
+ selectedImages.add(selectedUri)
+ } else Toast.makeText(
+ context, R.string.file_size_exceeded, Toast.LENGTH_SHORT
+ ).show()
+ val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ contentResolver?.takePersistableUriPermission(selectedUri, takeFlags)
+ }
+ }
+
+ val lazyListState = rememberLazyGridState()
+ val scope = rememberCoroutineScope()
+
+ fun scrollToLastItem() {
+ val lastIndex = selectedImages.size - 1
+ if (lastIndex >= 0) {
+ scope.launch {
+ lazyListState.animateScrollToItem(lastIndex)
+ }
+ }
+ }
+ LaunchedEffect(selectedImages.size) {
+ scrollToLastItem()
+ }
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(2),
+ state = lazyListState,
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(
+ bottom = padding.calculateBottomPadding(), top = 16.dp, start = 8.dp, end = 8.dp
+ )
+ ) {
+ items(selectedImages.size + 1) { index ->
+ if (index < selectedImages.size) {
+ GalleryItem(selectedImages[index],
+ onRemoveClick = { selectedImages.removeAt(index) },
+ onPlayClick = { onPlayClick(selectedImages[index]) },
+ onImageClick = { onImageClick(selectedImages[index]) })
+ } else {
+ if (data == GALLERY_ITEM && selectedImages.size < maxFileLimit) {
+ AddMedia(photoPickerLauncher)
+ } else if (selectedImages.size < maxFileLimit) {
+ AddDocument(documentPickerLauncher)
+ }
+ }
+ }
+ }
+}
+
+@SuppressLint("SuspiciousIndentation")
+@Composable
+fun GalleryItem(
+ fileUri: Uri,
+ onRemoveClick: () -> Unit,
+ onPlayClick: (fileUri:Uri) -> Unit = {},
+ onImageClick: (fileUri:Uri) -> Unit = {}
+) {
+
+ val context = LocalContext.current
+ val mimeType = getMimeType(context, Uri.parse(fileUri.toString()))
+ val allowedMimeTypes = listOf(
+ MIME_TYPE_PDF,
+ MIME_TYPE_DOC,
+ MIME_TYPE_DOCX,
+ )
+ Box(
+ modifier = Modifier
+ .padding(14.dp)
+ .background(Color.White)
+ .height(138.dp)
+ .border(1.dp, NaviChatColor.ctaPrimary, RoundedCornerShape(4.dp)),
+ ) {
+ if (mimeType != null) {
+ if (mimeType.startsWith(MIME_TYPE_IMAGE)) {
+ AsyncImage(
+ model = ImageRequest.Builder(LocalContext.current).data(fileUri).allowHardware(false).build(),
+ contentDescription = "Image preview",
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxSize()
+ .clickable(interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ onClick = { onImageClick(fileUri) }),
+ )
+ }
+ if (allowedMimeTypes.any { mimeType.startsWith(it) }) {
+ Box(modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.Center,
+ content = {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_doc_preview),
+ contentDescription = "Document preview",
+ )
+ Spacer(modifier = Modifier.size(10.dp))
+ val fileName = getFileName(context.contentResolver, fileUri)
+ Text(
+ text = fileName,
+ fontSize = 12.sp,
+ lineHeight = 18.sp,
+ fontFamily = composeFontFamily,
+ color = Color.Black,
+ modifier = Modifier.padding(horizontal = 10.dp),
+ textAlign = TextAlign.Center,
+ maxLines = 2,
+ )
+ }
+ })
+ }
+
+ if (mimeType.startsWith(MIME_TYPE_VIDEO)) {
+ val imageLoader = ImageLoader.Builder(context).components {
+ add(VideoFrameDecoder.Factory())
+ }.crossfade(true).build()
+
+ val painter = rememberAsyncImagePainter(
+ model = fileUri,
+ imageLoader = imageLoader
+ )
+ Box(
+ modifier= Modifier.clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = null,
+ onClick = { onPlayClick(fileUri) })
+ ) {
+ Image(
+ painter = painter,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.fillMaxSize()
+ )
+ Image(
+ painter = painterResource(id = R.drawable.ic_play_video_button),
+ contentDescription = "Play video",
+ modifier = Modifier.align(Alignment.Center)
+ )
+
+ val retriever = MediaMetadataRetriever()
+ retriever.setDataSource(context, fileUri)
+ val videoDurationString =
+ retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)
+ val videoDuration = videoDurationString?.toLong()
+ retriever.release()
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(top = 115.dp, end = 2.dp),
+ contentAlignment = Alignment.BottomEnd
+ ) {
+ val videoDurationInSeconds =
+ timeInString((videoDuration?.div(1000))?.toInt() ?: 0)
+ Text(
+ text = videoDurationInSeconds,
+ color = Color.White,
+ fontSize = 12.sp,
+ modifier = Modifier.padding(4.dp)
+ )
+ }
+ }
+ }
+ }
+ }
+
+ Box(
+ modifier = Modifier
+ .offset(8.dp, (-12).dp)
+ .fillMaxSize(),
+ contentAlignment = Alignment.TopEnd
+ ) {
+ IconButton(
+ onClick = onRemoveClick
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.ic_remove_image),
+ contentDescription = "Remove File",
+ )
+ }
+ }
+}
+
+@Composable
+fun AddMedia(photoPickerLauncher: ManagedActivityResultLauncher, Uri?>)
+{
+ Box(modifier = Modifier
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() }, indication = null
+ ) {
+ photoPickerLauncher.launch(
+ arrayOf(
+ IMAGE_TYPE,
+ VIDEO_TYPE,
+ )
+ )
+ }
+ .padding(14.dp)
+ .border(
+ width = 1.dp, color = NaviChatColor.ctaPrimary, shape = RoundedCornerShape(size = 4.dp)
+ )
+ .height(138.dp), contentAlignment = Alignment.Center, content = {
+ Column(
+ modifier = Modifier.padding(start = 15.dp, end = 15.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_add_image),
+ contentDescription = stringResource(id = R.string.add_media)
+ )
+ Spacer(modifier = Modifier.size(8.dp))
+ Text(
+ text = stringResource(id = R.string.add_media),
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ fontFamily = ttComposeFontFamily,
+ fontWeight = FontWeight(600),
+ color = NaviChatColor.textSecondary,
+ )
+ }
+ })
+}
+
+@Composable
+fun AddDocument(documentPickerLauncher: ManagedActivityResultLauncher, Uri?>)
+{
+ Box(modifier = Modifier
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() }, indication = null
+ ) {
+ documentPickerLauncher.launch(
+ arrayOf(
+ MIME_TYPE_PDF,
+ MIME_TYPE_DOC,
+ MIME_TYPE_DOCX,
+ )
+ )
+ }
+ .padding(14.dp)
+ .border(
+ width = 1.dp, color = NaviChatColor.ctaPrimary, shape = RoundedCornerShape(size = 4.dp)
+ )
+ .height(138.dp), contentAlignment = Alignment.Center, content = {
+ Column(
+ modifier = Modifier.padding(start = 15.dp, end = 15.dp),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_add_image),
+ contentDescription = stringResource(id = R.string.add_attachment),
+ )
+ Spacer(modifier = Modifier.size(8.dp))
+ Text(
+ text = stringResource(id = R.string.add_attachment),
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ fontFamily = ttComposeFontFamily,
+ fontWeight = FontWeight(600),
+ color = NaviChatColor.textSecondary,
+ textAlign = TextAlign.Center
+ )
+ }
+ })
+}
diff --git a/navi-chat/src/main/java/com/navi/chat/ui/compose/NaviChatCommonViews.kt b/navi-chat/src/main/java/com/navi/chat/ui/compose/NaviChatCommonViews.kt
new file mode 100644
index 0000000000..9adf3c9f75
--- /dev/null
+++ b/navi-chat/src/main/java/com/navi/chat/ui/compose/NaviChatCommonViews.kt
@@ -0,0 +1,171 @@
+package com.navi.chat.ui.compose
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.IconButton
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.TextUnit
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.navi.chat.R
+import com.navi.chat.ui.theme.NaviChatColor
+import com.navi.common.utils.ClickDebounce
+import com.navi.common.utils.get
+import com.navi.design.font.FontWeightEnum
+import com.navi.design.theme.getFontWeight
+import com.navi.design.theme.ttComposeFontFamily
+
+
+@Composable
+fun NaviChatHeader(
+ title: String,
+ navigationIcon: Int = R.drawable.ic_chat_back_arrow,
+ onNavigationIconClick: (() -> Unit),
+ modifier: Modifier,
+ backgroundColor: Color = Color.White
+) {
+ TopAppBar(title = {
+ Text(
+ text = title,
+ fontSize = 14.sp,
+ fontFamily = ttComposeFontFamily,
+ fontWeight = getFontWeight(FontWeightEnum.TT_REGULAR),
+ textAlign = TextAlign.Center,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(end = 60.dp)
+ )
+ }, navigationIcon = {
+ IconButton(onClick = { onNavigationIconClick.invoke() }) {
+ Image(painter = painterResource(navigationIcon), contentDescription = null)
+ }
+ }, modifier = modifier.height(48.dp),
+ elevation = 1.dp,
+ backgroundColor = backgroundColor
+ )
+}
+
+@Composable
+fun NaviChatFooter(
+ secondaryButtonText: String,
+ primaryButtonText: String,
+ onSecondaryButtonClicked: () -> Unit,
+ onPrimaryButtonClicked: () -> Unit,
+ primaryButtonEnabled: Boolean = true
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(start = 16.dp, end = 16.dp, top = 16.dp, bottom = 32.dp)
+ .background(Color.White),
+ ) {
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ Row {
+ SecondaryRoundedButton(
+ modifier = Modifier.weight(1f),
+ text = secondaryButtonText
+ ) {
+ onSecondaryButtonClicked.invoke()
+ }
+
+ Spacer(modifier = Modifier.width(16.dp))
+
+ ThemeRoundedButton(
+ modifier = Modifier.weight(1f),
+ text = primaryButtonText,
+ enabled = primaryButtonEnabled
+ ) {
+ onPrimaryButtonClicked.invoke()
+ }
+
+ }
+ }
+}
+
+@Composable
+fun ThemeRoundedButton(
+ modifier: Modifier = Modifier,
+ text: String,
+ cornerRadius: Dp = 4.dp,
+ paddingValues: PaddingValues = PaddingValues(vertical = 14.dp, horizontal = 10.dp),
+ fontSize: TextUnit = 14.sp,
+ enabled: Boolean = true,
+ onClick: () -> Unit,
+) {
+
+ val clickDebounce = remember { ClickDebounce.get() }
+
+ Button(
+ elevation = ButtonDefaults.elevation(defaultElevation = 0.dp),
+ onClick = { clickDebounce.processClick { onClick() } },
+ modifier = modifier,
+ shape = RoundedCornerShape(cornerRadius),
+ contentPadding = paddingValues,
+ enabled = enabled,
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = NaviChatColor.ctaPrimary,
+ disabledBackgroundColor = NaviChatColor.ctaDisabled
+ )
+ ) {
+ Text(
+ text = text,
+ fontSize = fontSize,
+ fontFamily = ttComposeFontFamily,
+ fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
+ color = NaviChatColor.textWhite,
+ letterSpacing = 0.sp
+ )
+ }
+}
+
+@Composable
+fun SecondaryRoundedButton(
+ modifier: Modifier = Modifier,
+ text: String,
+ cornerRadius: Dp = 4.dp,
+ paddingValues: PaddingValues = PaddingValues(vertical = 14.dp, horizontal = 10.dp),
+ onClick: () -> Unit
+) {
+ val clickDebounce = remember { ClickDebounce.get() }
+
+ Button(
+ elevation = ButtonDefaults.elevation(defaultElevation = 0.dp),
+ onClick = { clickDebounce.processClick { onClick() } },
+ modifier = modifier,
+ shape = RoundedCornerShape(cornerRadius),
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = NaviChatColor.ctaSecondary
+ ),
+ contentPadding = paddingValues
+ ) {
+ Text(
+ text = text,
+ fontSize = 14.sp,
+ fontFamily = ttComposeFontFamily,
+ fontWeight = getFontWeight(FontWeightEnum.NAVI_BODY_DEMI_BOLD),
+ color = NaviChatColor.ctaPrimary,
+ letterSpacing = 0.sp
+ )
+ }
+}
diff --git a/navi-chat/src/main/java/com/navi/chat/ui/fragments/ChatAttachmentBottomSheet.kt b/navi-chat/src/main/java/com/navi/chat/ui/fragments/ChatAttachmentBottomSheet.kt
index b9afc87654..4e4dd27e81 100644
--- a/navi-chat/src/main/java/com/navi/chat/ui/fragments/ChatAttachmentBottomSheet.kt
+++ b/navi-chat/src/main/java/com/navi/chat/ui/fragments/ChatAttachmentBottomSheet.kt
@@ -9,19 +9,17 @@ package com.navi.chat.ui.fragments
import android.Manifest
-import android.annotation.SuppressLint
import android.app.Activity
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
-import android.database.Cursor
import android.net.Uri
import android.os.Build
import android.os.Bundle
-import android.os.Environment
-import android.provider.OpenableColumns
import android.view.View
import android.view.ViewStub
-import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.result.contract.ActivityResultContract
+import androidx.annotation.CallSuper
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
@@ -35,6 +33,7 @@ import com.navi.chat.databinding.ChatAttachmentBottomSheetBinding
import com.navi.chat.di.components.DaggerNaviChatComponent
import com.navi.chat.di.dependencies.NaviChatModuleDependencies
import com.navi.chat.di.modules.NaviChatModule
+import com.navi.chat.interfaces.AttachmentOptionClickListener
import com.navi.chat.interfaces.ChatAttachmentOptionsInterface
import com.navi.chat.models.ChatAttachmentData
import com.navi.chat.utils.FILE_MANAGER
@@ -43,7 +42,6 @@ import com.navi.chat.viewmodels.AttachmentPickerViewModel
import dagger.hilt.android.EntryPointAccessors
import kotlinx.coroutines.launch
import timber.log.Timber
-import java.io.File
class ChatAttachmentBottomSheet : BaseBottomSheet(), ChatAttachmentOptionsInterface {
@@ -53,11 +51,15 @@ class ChatAttachmentBottomSheet : BaseBottomSheet(), ChatAttachmentOptionsInterf
private val attachmentPickerViewModel by lazy {
ViewModelProvider(requireActivity())[AttachmentPickerViewModel::class.java]
}
-
+ private var attachmentOptionClickListener: AttachmentOptionClickListener? = null
override val screenName: String
get() = TAG
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ attachmentOptionClickListener = context as? AttachmentOptionClickListener
+ }
override fun setContainerView(viewStub: ViewStub) {
viewStub.layoutResource = R.layout.chat_attachment_bottom_sheet
binding = DataBindingUtil.getBinding(viewStub.inflate())!!
@@ -97,7 +99,7 @@ class ChatAttachmentBottomSheet : BaseBottomSheet(), ChatAttachmentOptionsInterf
private fun createAttachmentOptionList(): ArrayList {
val attachmentDataArrayList = ArrayList()
- AppCompatResources.getDrawable(requireContext(), R.drawable.ic_gallery_icon)?.let {
+ AppCompatResources.getDrawable(requireContext(), R.drawable.ic_gallery_icon_new)?.let {
attachmentDataArrayList.add(
ChatAttachmentData(
it,
@@ -106,7 +108,7 @@ class ChatAttachmentBottomSheet : BaseBottomSheet(), ChatAttachmentOptionsInterf
)
)
}
- AppCompatResources.getDrawable(requireContext(), R.drawable.ic_file_manager_icon)?.let {
+ AppCompatResources.getDrawable(requireContext(), R.drawable.ic_file_manager_icon_new)?.let {
attachmentDataArrayList.add(
ChatAttachmentData(
it,
@@ -122,44 +124,15 @@ class ChatAttachmentBottomSheet : BaseBottomSheet(), ChatAttachmentOptionsInterf
if (isLegacyExternalStoragePermissionRequired()) {
requestLegacyWriteExternalStoragePermission()
} else {
- val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
- intent.addCategory(Intent.CATEGORY_OPENABLE)
- intent.type = "image/*"
- intent.putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("image/*", "video/*"))
- resultLauncher.launch(Intent.createChooser(intent, "Select image or video"))
+ attachmentOptionClickListener?.onGalleryAttachmentOptionClicked()
}
}
- private var resultLauncher =
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
- if (result.resultCode == Activity.RESULT_OK) {
- attachmentPickerViewModel.setImagePickerData(
- result.data, result.data?.data?.let { getFileName(it) }
- )
- }
- }
-
private fun openFile() {
if (isLegacyExternalStoragePermissionRequired()) {
requestLegacyWriteExternalStoragePermission()
} else {
- val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
- addCategory(Intent.CATEGORY_OPENABLE)
- type = "application/*"
- putExtra(
- Intent.EXTRA_MIME_TYPES,
- arrayOf(
- DOC,
- DOCX,
- PDF
- )
- )
- putExtra(
- Intent.ACTION_OPEN_DOCUMENT,
- requireContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
- )
- }
- resultLauncher1.launch(intent)
+ attachmentOptionClickListener?.onFileAttachmentOptionClicked()
}
}
@@ -183,36 +156,6 @@ class ChatAttachmentBottomSheet : BaseBottomSheet(), ChatAttachmentOptionsInterf
}
}
- private var resultLauncher1 =
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
- if (result.resultCode == Activity.RESULT_OK) {
- attachmentPickerViewModel.setPdfPickerData(
- result.data, result.data?.data?.let { getFileName(it) }
- )
- }
- }
-
- @SuppressLint("Range")
- private fun getFileName(uri: Uri): String {
- val uriString = uri.toString()
- var displayName = ""
- if (uriString.startsWith("content://")) {
- var cursor: Cursor? = null
- try {
- cursor = activity?.contentResolver?.query(uri, null, null, null, null)
- if (cursor != null && cursor.moveToFirst()) {
- displayName =
- cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
- }
- } finally {
- cursor?.close()
- }
- } else if (uriString.startsWith("file://")) {
- displayName = File(uriString).name
- }
- return displayName
- }
-
override fun onClickChatAttachmentOption(attachmentsType: String) {
when (attachmentsType) {
GALLERY -> {
@@ -233,4 +176,4 @@ class ChatAttachmentBottomSheet : BaseBottomSheet(), ChatAttachmentOptionsInterf
const val DOC = "application/msword"
const val DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
}
-}
+}
\ No newline at end of file
diff --git a/navi-chat/src/main/java/com/navi/chat/ui/fragments/NaviChatFragment.kt b/navi-chat/src/main/java/com/navi/chat/ui/fragments/NaviChatFragment.kt
index 4e106893ed..8acc011911 100644
--- a/navi-chat/src/main/java/com/navi/chat/ui/fragments/NaviChatFragment.kt
+++ b/navi-chat/src/main/java/com/navi/chat/ui/fragments/NaviChatFragment.kt
@@ -380,6 +380,7 @@ class NaviChatFragment : ChatBaseFragment(), WidgetCallback, MessageOperation, T
fileSizes.clear()
it?.let {
fileSizes.putAll(it.fileSizeInKB.orEmpty())
+ it.fileSizeInKB?.let { it1 -> attachmentPickerViewModel.setFileSizes(it1) }
it.botTypingCuesDelayInMs?.let { botDelayInMs ->
delayForTypingCuesForBot = botDelayInMs
Timber.d("Updated delayForTypingCuesForBot to $botDelayInMs")
diff --git a/navi-chat/src/main/java/com/navi/chat/ui/theme/NaviChatColor.kt b/navi-chat/src/main/java/com/navi/chat/ui/theme/NaviChatColor.kt
new file mode 100644
index 0000000000..04723c1cde
--- /dev/null
+++ b/navi-chat/src/main/java/com/navi/chat/ui/theme/NaviChatColor.kt
@@ -0,0 +1,11 @@
+package com.navi.chat.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+object NaviChatColor {
+ val textSecondary = Color(0xFF444444)
+ val textWhite = Color(0xFFFFFFFF)
+ val ctaPrimary = Color(0xFF1F002A)
+ val ctaSecondary = Color(0xFFF5F5F5)
+ val ctaDisabled = Color(0xFFB5ACB9)
+}
\ No newline at end of file
diff --git a/navi-chat/src/main/java/com/navi/chat/utils/Constants.kt b/navi-chat/src/main/java/com/navi/chat/utils/Constants.kt
index cc1befa9ca..84292c6013 100644
--- a/navi-chat/src/main/java/com/navi/chat/utils/Constants.kt
+++ b/navi-chat/src/main/java/com/navi/chat/utils/Constants.kt
@@ -50,6 +50,20 @@ const val BOT = "BOT"
const val DIGITAL_GOLD = "DIGITAL_GOLD"
const val FILENAME = "filename"
const val NODE_UUID_PARAM = "nodeUuid"
+const val GALLERY_ITEM = "image"
+const val DOCUMENT_ITEM = "document"
+const val URI_LIST = "uriList"
+const val FILE_SIZES = "fileSizes"
+const val FILE_URI = "fileUri"
+const val IMAGE_TYPE = "image/*"
+const val VIDEO_TYPE = "video/*"
+const val MIME_TYPE_IMAGE = "image/"
+const val MIME_TYPE_VIDEO = "video/"
+const val MIME_TYPE_PDF = "application/pdf"
+const val MIME_TYPE_DOC = "application/msword"
+const val MIME_TYPE_DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
+const val maxFileLimit = 6
+const val maxFileSizeInKb = 15000
/* Video Player Instance Save Constant */
const val CURRENT_VIDEO_POSITION_IN_MS = "currentVideoPositionInMs"
@@ -59,3 +73,4 @@ const val TIME_FORMAT_TO_DISPLAY = "%02d:%02d"
/* Screen names */
const val NAVI_CHAT_VIEW_VIDEO_ACTIVITY = "naviChatViewVideoActivity"
+const val NAVI_CHAT_ATTACHMENT_CONFIRMATION_ACTIVITY = "naviChatAttachmentConfirmationActivity"
diff --git a/navi-chat/src/main/java/com/navi/chat/viewmodels/AttachmentPickerViewModel.kt b/navi-chat/src/main/java/com/navi/chat/viewmodels/AttachmentPickerViewModel.kt
index c46b718941..dbcaa379d3 100644
--- a/navi-chat/src/main/java/com/navi/chat/viewmodels/AttachmentPickerViewModel.kt
+++ b/navi-chat/src/main/java/com/navi/chat/viewmodels/AttachmentPickerViewModel.kt
@@ -12,6 +12,7 @@ import android.app.Application
import android.content.Intent
import android.net.Uri
import android.widget.Toast
+import androidx.core.net.toUri
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.navi.chat.R
@@ -39,6 +40,7 @@ class AttachmentPickerViewModel(application: Application) : AndroidViewModel(app
private val defaultMaxFileSizeInMb = 15
var maxFileSizeInMb = defaultMaxFileSizeInMb
+ private var fileSizes = HashMap()
val dispatcher = CoroutineDispatcherProvider()
val fileHelper = ChatFileHelper(dispatcher)
private var isToastVisible = false
@@ -116,14 +118,21 @@ class AttachmentPickerViewModel(application: Application) : AndroidViewModel(app
}
}
- fun setImagePickerData(data: Intent?, fileName: String?) {
+ fun setImagePickerData(data: String?, fileName: String?) {
viewModelScope.launch(dispatcher.io) {
- val imageData = data?.data
- imageData?.let { emitFileData(imageData, fileName, FileHelper.JPG_EXTENSION) }
+ data?.let { emitFileData(data.toUri(), fileName, FileHelper.JPG_EXTENSION) }
dismissDialog()
}
}
+ fun setFileSizes(sizes: HashMap) {
+ fileSizes = sizes
+ }
+
+ fun getFileSizes(): HashMap {
+ return fileSizes
+ }
+
private fun dismissDialog() {
viewModelScope.launch(dispatcher.main) { _dismissBottomSheetDialog.emit(true) }
}
diff --git a/navi-chat/src/main/res/drawable/ic_add_image.xml b/navi-chat/src/main/res/drawable/ic_add_image.xml
new file mode 100644
index 0000000000..0aee6b29b1
--- /dev/null
+++ b/navi-chat/src/main/res/drawable/ic_add_image.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/navi-chat/src/main/res/drawable/ic_doc_preview.xml b/navi-chat/src/main/res/drawable/ic_doc_preview.xml
new file mode 100644
index 0000000000..71da414b10
--- /dev/null
+++ b/navi-chat/src/main/res/drawable/ic_doc_preview.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/navi-chat/src/main/res/drawable/ic_file_manager_icon_new.xml b/navi-chat/src/main/res/drawable/ic_file_manager_icon_new.xml
new file mode 100644
index 0000000000..ec0b658af3
--- /dev/null
+++ b/navi-chat/src/main/res/drawable/ic_file_manager_icon_new.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/navi-chat/src/main/res/drawable/ic_gallery_icon_new.xml b/navi-chat/src/main/res/drawable/ic_gallery_icon_new.xml
new file mode 100644
index 0000000000..b3440ad62e
--- /dev/null
+++ b/navi-chat/src/main/res/drawable/ic_gallery_icon_new.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/navi-chat/src/main/res/drawable/ic_pause_icon_purple.xml b/navi-chat/src/main/res/drawable/ic_pause_icon_purple.xml
new file mode 100644
index 0000000000..f0ea6d943b
--- /dev/null
+++ b/navi-chat/src/main/res/drawable/ic_pause_icon_purple.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/navi-chat/src/main/res/drawable/ic_play_button.xml b/navi-chat/src/main/res/drawable/ic_play_button.xml
new file mode 100644
index 0000000000..3679523a43
--- /dev/null
+++ b/navi-chat/src/main/res/drawable/ic_play_button.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/navi-chat/src/main/res/drawable/ic_play_video_button.xml b/navi-chat/src/main/res/drawable/ic_play_video_button.xml
new file mode 100644
index 0000000000..13924ef4aa
--- /dev/null
+++ b/navi-chat/src/main/res/drawable/ic_play_video_button.xml
@@ -0,0 +1,13 @@
+
+
+
+
diff --git a/navi-chat/src/main/res/drawable/ic_play_video_purple.xml b/navi-chat/src/main/res/drawable/ic_play_video_purple.xml
new file mode 100644
index 0000000000..ee5d7679e3
--- /dev/null
+++ b/navi-chat/src/main/res/drawable/ic_play_video_purple.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/navi-chat/src/main/res/drawable/ic_remove_image.xml b/navi-chat/src/main/res/drawable/ic_remove_image.xml
new file mode 100644
index 0000000000..2f6044cc07
--- /dev/null
+++ b/navi-chat/src/main/res/drawable/ic_remove_image.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
diff --git a/navi-chat/src/main/res/drawable/ic_right_arrow_purple_white_bg.xml b/navi-chat/src/main/res/drawable/ic_right_arrow_purple_white_bg.xml
new file mode 100644
index 0000000000..b958eeb29f
--- /dev/null
+++ b/navi-chat/src/main/res/drawable/ic_right_arrow_purple_white_bg.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/navi-chat/src/main/res/drawable/ic_tooltip_slider_purple.xml b/navi-chat/src/main/res/drawable/ic_tooltip_slider_purple.xml
new file mode 100644
index 0000000000..d09efb7105
--- /dev/null
+++ b/navi-chat/src/main/res/drawable/ic_tooltip_slider_purple.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/navi-chat/src/main/res/layout/activity_navi_chat_view_video.xml b/navi-chat/src/main/res/layout/activity_navi_chat_view_video.xml
index 3a1cea9b01..fddae5af46 100644
--- a/navi-chat/src/main/res/layout/activity_navi_chat_view_video.xml
+++ b/navi-chat/src/main/res/layout/activity_navi_chat_view_video.xml
@@ -57,7 +57,7 @@
android:id="@+id/ivPlayPauseVideo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:src="@drawable/ic_pause_icon"
+ android:src="@drawable/ic_pause_icon_purple"
android:visibility="gone"
tools:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
@@ -84,8 +84,8 @@
app:layout_constraintEnd_toStartOf="@id/totalDuration"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="@dimen/dp_34"
- android:progressTint="@color/outrageous_orange"
- android:thumb="@drawable/ic_tooltip_slider_thumb"
+ android:progressTint="@color/dark_purple"
+ android:thumb="@drawable/ic_tooltip_slider_purple"
app:layout_constraintHorizontal_bias="0.0"
tools:progress="50"
android:progressBackgroundTint="@color/white"
diff --git a/navi-chat/src/main/res/layout/chat_attachment_bottom_sheet.xml b/navi-chat/src/main/res/layout/chat_attachment_bottom_sheet.xml
index 5ec5286a8d..d6731f4cc2 100644
--- a/navi-chat/src/main/res/layout/chat_attachment_bottom_sheet.xml
+++ b/navi-chat/src/main/res/layout/chat_attachment_bottom_sheet.xml
@@ -23,7 +23,7 @@
android:layout_marginStart="@dimen/layout_dp_16"
android:layout_marginTop="@dimen/layout_dp_16"
android:textColor="@color/dark_gray"
- android:fontFamily="@font/tt_regular"
+ android:fontFamily="@font/tt_medium"
android:textSize="@dimen/sp_18"
android:text="@string/add_files"
app:layout_constraintEnd_toEndOf="parent"
diff --git a/navi-chat/src/main/res/layout/fragment_common_navi_chat.xml b/navi-chat/src/main/res/layout/fragment_common_navi_chat.xml
index 6f0d159299..4a4ee72e72 100644
--- a/navi-chat/src/main/res/layout/fragment_common_navi_chat.xml
+++ b/navi-chat/src/main/res/layout/fragment_common_navi_chat.xml
@@ -26,6 +26,7 @@
+ tools:src="@drawable/ic_gallery_icon_new" />
Error occurred while playing video
The link you are trying to access has expired.
%d/%d characters left
+ Cancel
+ Send
+ Add Attachment
+ Add Media
+ File size is too large
00:00
\ No newline at end of file
diff --git a/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/NaviChatReceivedMessageWithAttachmentLayout.kt b/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/NaviChatReceivedMessageWithAttachmentLayout.kt
index 12a7d6e7bd..987c24d1db 100644
--- a/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/NaviChatReceivedMessageWithAttachmentLayout.kt
+++ b/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/NaviChatReceivedMessageWithAttachmentLayout.kt
@@ -84,7 +84,7 @@ class NaviChatReceivedMessageWithAttachmentLayout @JvmOverloads constructor(
when (state) {
VIEW_STATE -> {
- tvReceivedMessage.progressImage.setBackgroundResource(R.drawable.ic_preview)
+ tvReceivedMessage.progressImage.setBackgroundResource(R.drawable.ic_preview_purple)
tvReceivedMessage.progressState.progress = ProgressBar.GONE
tvReceivedMessage.progressImage.setOnClickListener {
naviChatFileAttachmentWidget.widgetData?.fileName?.let { uri ->
@@ -95,7 +95,7 @@ class NaviChatReceivedMessageWithAttachmentLayout @JvmOverloads constructor(
}
}
DOWNLOAD_STATE, FAILED_STATE -> {
- tvReceivedMessage.progressImage.setBackgroundResource(R.drawable.ic_download)
+ tvReceivedMessage.progressImage.setBackgroundResource(R.drawable.ic_download_purple)
tvReceivedMessage.progressState.progress = ProgressBar.GONE
tvReceivedMessage.progressImage.setOnClickListener {
naviChatFileAttachmentWidget.widgetData.let { widgetData ->
@@ -134,9 +134,9 @@ class NaviChatReceivedMessageWithAttachmentLayout @JvmOverloads constructor(
private fun getBackgroundDrawable(fileType: String?): Int {
return when (fileType) {
- AWSFileType.MP4.value -> R.drawable.ic_video_preview
+ AWSFileType.MP4.value -> R.drawable.ic_video_preview_purple
AWSFileType.JPG.value, AWSFileType.JPEG.value,
- AWSFileType.PNG.value -> R.drawable.ic_image_preview
+ AWSFileType.PNG.value -> R.drawable.ic_image_preview_purple
else -> R.drawable.ic_document_preview
}
}
diff --git a/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/NaviChatSentMessageWithAttachmentLayout.kt b/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/NaviChatSentMessageWithAttachmentLayout.kt
index c3ba6f4995..d7909d8a66 100644
--- a/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/NaviChatSentMessageWithAttachmentLayout.kt
+++ b/navi-widgets/src/main/java/com/navi/naviwidgets/widgets/NaviChatSentMessageWithAttachmentLayout.kt
@@ -86,7 +86,11 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
when (state) {
VIEW_STATE -> {
- tvSentMessage.progressImage.setBackgroundResource(R.drawable.ic_preview)
+ if (naviChatFileAttachmentWidget.widgetData?.fileType == AWSFileType.MP4.value) {
+ tvSentMessage.progressImage.setBackgroundResource(R.drawable.ic_play_preview_purple)
+ } else {
+ tvSentMessage.progressImage.setBackgroundResource(R.drawable.ic_preview_purple)
+ }
tvSentMessage.progressState.progress = ProgressBar.GONE
tvSentMessage.progressImage.setOnClickListener {
naviChatFileAttachmentWidget.id?.let {
@@ -97,7 +101,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
}
}
FAILURE_AND_RETRY_STATE -> {
- tvSentMessage.progressImage.setBackgroundResource(R.drawable.ic_retry)
+ tvSentMessage.progressImage.setBackgroundResource(R.drawable.ic_retry_new)
tvSentMessage.progressState.progress = ProgressBar.GONE
tvSentMessage.progressImage.setOnClickListener {
naviChatFileAttachmentWidget.id?.let {
@@ -106,7 +110,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
}
}
UPLOADING_STATE -> {
- tvSentMessage.progressImage.setBackgroundResource(R.drawable.ic_cancel)
+ tvSentMessage.progressImage.setBackgroundResource(R.drawable.ic_cancel_purple)
tvSentMessage.progressState.progress = ProgressBar.VISIBLE
tvSentMessage.progressImage.setOnClickListener {
naviChatFileAttachmentWidget.id?.let {
@@ -116,7 +120,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
}
DOWNLOAD_STATE,
FAILED_STATE -> {
- tvSentMessage.progressImage.setBackgroundResource(R.drawable.ic_download)
+ tvSentMessage.progressImage.setBackgroundResource(R.drawable.ic_download_purple)
tvSentMessage.progressState.progress = ProgressBar.GONE
tvSentMessage.progressImage.setOnClickListener {
naviChatFileAttachmentWidget.widgetData?.fileUuid?.let {
@@ -163,10 +167,10 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
private fun getBackgroundDrawable(fileType: String?): Int {
return when (fileType) {
- AWSFileType.MP4.value -> R.drawable.ic_video_preview
+ AWSFileType.MP4.value -> R.drawable.ic_video_preview_purple
AWSFileType.JPG.value,
AWSFileType.JPEG.value,
- AWSFileType.PNG.value -> R.drawable.ic_image_preview
+ AWSFileType.PNG.value -> R.drawable.ic_image_preview_purple
else -> R.drawable.ic_document_preview
}
}
diff --git a/navi-widgets/src/main/res/drawable/ic_cancel_purple.xml b/navi-widgets/src/main/res/drawable/ic_cancel_purple.xml
new file mode 100644
index 0000000000..8d57750be3
--- /dev/null
+++ b/navi-widgets/src/main/res/drawable/ic_cancel_purple.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/navi-widgets/src/main/res/drawable/ic_document_preview.xml b/navi-widgets/src/main/res/drawable/ic_document_preview.xml
index 629965b4af..6603486e94 100644
--- a/navi-widgets/src/main/res/drawable/ic_document_preview.xml
+++ b/navi-widgets/src/main/res/drawable/ic_document_preview.xml
@@ -1,27 +1,9 @@
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
-
-
-
+ android:pathData="M6,2C4.906,2 4,2.906 4,4V20C4,21.094 4.906,22 6,22H18C19.094,22 20,21.094 20,20V6.586L15.414,2H6ZM6,4H14V8H18V20H6V4ZM8,10V12H16V10H8ZM8,13V15H16V13H8ZM8,16V18H13V16H8Z"
+ android:fillColor="#1F002A"/>
diff --git a/navi-widgets/src/main/res/drawable/ic_download_purple.xml b/navi-widgets/src/main/res/drawable/ic_download_purple.xml
new file mode 100644
index 0000000000..cc5c9324fd
--- /dev/null
+++ b/navi-widgets/src/main/res/drawable/ic_download_purple.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/navi-widgets/src/main/res/drawable/ic_image_preview_purple.xml b/navi-widgets/src/main/res/drawable/ic_image_preview_purple.xml
new file mode 100644
index 0000000000..f76ca5a2b3
--- /dev/null
+++ b/navi-widgets/src/main/res/drawable/ic_image_preview_purple.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
diff --git a/navi-widgets/src/main/res/drawable/ic_navi_chat_sent_message_bg.xml b/navi-widgets/src/main/res/drawable/ic_navi_chat_sent_message_bg.xml
index c2aee9e40c..d49e56ec74 100644
--- a/navi-widgets/src/main/res/drawable/ic_navi_chat_sent_message_bg.xml
+++ b/navi-widgets/src/main/res/drawable/ic_navi_chat_sent_message_bg.xml
@@ -1,9 +1,14 @@
+
+
-
+ android:bottomLeftRadius="@dimen/dp_20"
+ android:bottomRightRadius="@dimen/dp_4"
+ android:topLeftRadius="@dimen/dp_20"
+ android:topRightRadius="@dimen/dp_20" />
+
\ No newline at end of file
diff --git a/navi-widgets/src/main/res/drawable/ic_play_preview_purple.xml b/navi-widgets/src/main/res/drawable/ic_play_preview_purple.xml
new file mode 100644
index 0000000000..f485e33630
--- /dev/null
+++ b/navi-widgets/src/main/res/drawable/ic_play_preview_purple.xml
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/navi-widgets/src/main/res/drawable/ic_preview_purple.xml b/navi-widgets/src/main/res/drawable/ic_preview_purple.xml
new file mode 100644
index 0000000000..ced0b4c906
--- /dev/null
+++ b/navi-widgets/src/main/res/drawable/ic_preview_purple.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/navi-widgets/src/main/res/drawable/ic_retry_new.xml b/navi-widgets/src/main/res/drawable/ic_retry_new.xml
new file mode 100644
index 0000000000..aeb0ecded5
--- /dev/null
+++ b/navi-widgets/src/main/res/drawable/ic_retry_new.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/navi-widgets/src/main/res/drawable/ic_video_preview_purple.xml b/navi-widgets/src/main/res/drawable/ic_video_preview_purple.xml
new file mode 100644
index 0000000000..9a92dd7d11
--- /dev/null
+++ b/navi-widgets/src/main/res/drawable/ic_video_preview_purple.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
diff --git a/navi-widgets/src/main/res/layout/layout_navi_chat_item_message_with_attachment.xml b/navi-widgets/src/main/res/layout/layout_navi_chat_item_message_with_attachment.xml
index bc7c54a3fd..1d769b54e7 100644
--- a/navi-widgets/src/main/res/layout/layout_navi_chat_item_message_with_attachment.xml
+++ b/navi-widgets/src/main/res/layout/layout_navi_chat_item_message_with_attachment.xml
@@ -18,8 +18,8 @@
app:layout_constraintEnd_toStartOf="@id/tvMessage"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
- android:src="@drawable/ic_image_preview"
- tools:src="@drawable/ic_image_preview" />
+ android:src="@drawable/ic_image_preview_purple"
+ tools:src="@drawable/ic_image_preview_purple" />
+ tools:src="@drawable/ic_cancel_purple" />
\ No newline at end of file
diff --git a/navi-widgets/src/main/res/layout/layout_navi_chat_sent_message_with_attachment.xml b/navi-widgets/src/main/res/layout/layout_navi_chat_sent_message_with_attachment.xml
index 4550cc7a6d..f15d2dfa55 100644
--- a/navi-widgets/src/main/res/layout/layout_navi_chat_sent_message_with_attachment.xml
+++ b/navi-widgets/src/main/res/layout/layout_navi_chat_sent_message_with_attachment.xml
@@ -54,7 +54,7 @@
android:layout_gravity="end"
android:paddingLeft="@dimen/dp_4"
app:layout_constraintBottom_toBottomOf="@id/tvTimestamp"
- app:layout_constraintTop_toTopOf="@id/tvTimestamp"
+ app:layout_constraintTop_toBottomOf="@id/tvSentMessage"
app:layout_constraintEnd_toEndOf="@id/tvSentMessage"
android:visibility="gone"
tools:visibility="visible"