@ -25,6 +25,7 @@ import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.utun.chat.data.Message
@ -36,7 +37,8 @@ import kotlinx.coroutines.delay
fun MessageBubble ( message : Message , playbackState : ChatViewModel . PlaybackState = ChatViewModel . PlaybackState ( ) ,
onPlayVoice : ( String , Int ) -> Unit = { _ , _ -> } ,
onSeekVoice : ( String , Int , Int ) -> Unit = { _ , _ , _ -> } ,
onDownloadFile : ( Message ) -> Unit = { } ) {
onDownloadFile : ( Message ) -> Unit = { } ,
onOpenFile : ( Message ) -> Unit = { } ) {
val bgColor = if ( message . isOutgoing ) Color ( 0xFF4A90D9 ) else Color ( 0xFFE8E8E8 )
val textColor = if ( message . isOutgoing ) Color . White else Color . Black
val alignment = if ( message . isOutgoing ) Arrangement . End else Arrangement . Start
@ -63,10 +65,8 @@ fun MessageBubble(message: Message, playbackState: ChatViewModel.PlaybackState =
when {
isVoice -> VoiceBubble ( message , textColor , isActive , playbackState , onPlayVoice , onSeekVoice )
isMedia -> FileBubble ( message , textColor , onDownloadFile )
isMedia -> FileBubble ( message , textColor , onDownloadFile , onOpenFile )
else -> {
Text ( message . author , fontSize = 13. sp , fontWeight = FontWeight . Bold , color = textColor . copy ( alpha = 0.7f ) )
Spacer ( Modifier . height ( 2. dp ) )
Text ( message . text , fontSize = 17. sp , color = textColor )
}
}
@ -171,7 +171,8 @@ private fun VoiceBubble(message: Message, textColor: Color, isActive: Boolean,
}
@Composable
private fun FileBubble ( message : Message , textColor : Color , onDownload : ( Message ) -> Unit = { } ) {
private fun FileBubble ( message : Message , textColor : Color , onDownload : ( Message ) -> Unit = { } ,
onOpen : ( Message ) -> Unit = { } ) {
val state = message . downloadState
val stateIcon = when {
state == " downloaded " -> " \u2705 "
@ -179,13 +180,18 @@ private fun FileBubble(message: Message, textColor: Color, onDownload: (Message)
else -> " \u2B07 \uFE0F "
}
val stateText = when {
state == " downloaded " -> " Downloaded "
state == " downloaded " -> " Tap to save "
state == " downloading " -> " Downloading... "
else -> " Tap to download "
}
Column (
modifier = Modifier . clickable { if ( state . isEmpty ( ) ) onDownload ( message ) }
modifier = Modifier . clickable {
when {
state == " downloaded " -> onOpen ( message )
state . isEmpty ( ) -> onDownload ( message )
}
}
) {
Row ( verticalAlignment = Alignment . CenterVertically ) {
Text ( stateIcon , fontSize = 18. sp )
@ -235,6 +241,9 @@ fun InputBar(
onVoiceSend : ( ) -> Unit = { } ,
onVoiceCancel : ( ) -> Unit = { } ,
onAttachClicked : ( ) -> Unit = { } ,
pendingAttachmentName : String = " " ,
pendingAttachmentSize : Long = 0L ,
onClearAttachment : ( ) -> Unit = { } ,
modifier : Modifier = Modifier
) {
val ctx = LocalContext . current
@ -251,15 +260,51 @@ fun InputBar(
hasMicPerm = granted
}
val filePicker = rememberLauncherForActivityResult ( ActivityResultContracts . GetContent ( ) ) { _ -> }
val hasAttachment = pendingAttachmentName . isNotEmpty ( )
Column ( modifier = modifier . fillMaxWidth ( ) . padding ( 8. dp ) ) {
/* Attachment preview bar */
if ( hasAttachment ) {
Surface (
modifier = Modifier . fillMaxWidth ( ) . height ( 36. dp ) . padding ( bottom = 4. dp ) ,
shape = RoundedCornerShape ( 8. dp ) ,
color = MaterialTheme . colorScheme . primaryContainer
) {
Row (
modifier = modifier . fillMaxWidth ( ) . padding ( 8. dp ) ,
modifier = Modifier . fillMaxSize ( ) . padding ( horizontal = 8. dp ) ,
verticalAlignment = Alignment . CenterVertically
) {
Text ( " \uD83D \uDCCE " , fontSize = 14. sp )
Spacer ( Modifier . width ( 6. dp ) )
Text (
pendingAttachmentName ,
fontSize = 14. sp ,
maxLines = 1 ,
overflow = TextOverflow . Ellipsis ,
modifier = Modifier . weight ( 1f )
)
if ( pendingAttachmentSize > 0 ) {
Text (
formatFileSize ( pendingAttachmentSize ) ,
fontSize = 12. sp ,
color = MaterialTheme . colorScheme . onPrimaryContainer . copy ( alpha = 0.6f )
)
Spacer ( Modifier . width ( 8. dp ) )
}
IconButton (
onClick = onClearAttachment ,
modifier = Modifier . size ( 24. dp )
) {
Text ( " \u2715 " , fontSize = 14. sp )
}
}
}
}
Row ( modifier = Modifier . fillMaxWidth ( ) , verticalAlignment = Alignment . CenterVertically ) {
/* Attach button */
if ( !is Recording ) {
IconButton ( onClick = { filePicker . launch ( " */* " ) } , enabled = enabled ) {
IconButton ( onClick = onAttachClicked , enabled = enabled ) {
Text ( " \uD83D \uDCCE " , fontSize = 20. sp )
}
} else {
@ -314,7 +359,7 @@ fun InputBar(
IconButton ( onClick = onVoiceSend ) {
Text ( " \u25B6 " , fontSize = 20. sp , color = Color ( 0xFF4CAF50 ) )
}
} else if ( !is Recording && text . isNotBlank ( ) ) {
} else if ( !is Recording && ( text . isNotBlank ( ) || hasAttachment ) ) {
/* Send button — round, primary color */
val sendColor = MaterialTheme . colorScheme . primary
Box (
@ -375,6 +420,7 @@ fun InputBar(
}
}
}
}
private fun formatDuration ( ms : Int ) : String {
val secs = ms / 1000