app: MessageEntry: Adds UI support for uploading attachments
Still need backend support to finish this.
This commit is contained in:
@@ -11,12 +11,14 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import net.buzzert.kordophonedroid.R
|
||||
import net.buzzert.kordophonedroid.ui.conversationlist.ConversationListItem
|
||||
import net.buzzert.kordophonedroid.ui.conversationlist.ConversationListScreen
|
||||
import net.buzzert.kordophonedroid.ui.conversationlist.NoContentView
|
||||
import net.buzzert.kordophonedroid.ui.messagelist.AttachmentRowItem
|
||||
import net.buzzert.kordophonedroid.ui.messagelist.MessageEntry
|
||||
import net.buzzert.kordophonedroid.ui.messagelist.MessageListItem
|
||||
import net.buzzert.kordophonedroid.ui.messagelist.MessageMetadata
|
||||
@@ -77,11 +79,19 @@ private fun MessageListScreenPreview() {
|
||||
).reversed()
|
||||
|
||||
Scaffold() {
|
||||
MessageTranscript(messages = messages, paddingValues = it, showSenders = true, onSendMessage = {})
|
||||
MessageTranscript(
|
||||
messages = messages,
|
||||
paddingValues = it,
|
||||
showSenders = true,
|
||||
attachmentUris = setOf(),
|
||||
onAddAttachment = {},
|
||||
onClearAttachments = {},
|
||||
onSendMessage = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun MessageEntryPreview() {
|
||||
var textState by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||
@@ -91,6 +101,18 @@ private fun MessageEntryPreview() {
|
||||
MessageEntry(onSend = {}, onTextChanged = {}, textFieldValue = textState)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun MessageEntryWithAttachmentsPreview() {
|
||||
var textState by rememberSaveable(stateSaver = TextFieldValue.Saver) {
|
||||
mutableStateOf(TextFieldValue("Attachments"))
|
||||
}
|
||||
|
||||
MessageEntry(onSend = {}, onTextChanged = {}, textFieldValue = textState, attachmentItems = listOf(
|
||||
AttachmentRowItem(painterResource(id = R.drawable.sedona), "id")
|
||||
))
|
||||
}
|
||||
|
||||
// - No content
|
||||
|
||||
@Preview
|
||||
|
||||
@@ -1,47 +1,132 @@
|
||||
package net.buzzert.kordophonedroid.ui.messagelist
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material3.ElevatedButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import net.buzzert.kordophonedroid.R
|
||||
|
||||
data class AttachmentRowItem(
|
||||
val painter: Painter,
|
||||
val id: String,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun AttachmentRow(
|
||||
attachmentItems: List<AttachmentRowItem>,
|
||||
onClear: () -> Unit,
|
||||
) {
|
||||
Divider()
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(120.dp)
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colors.onSurface.copy(0.08f))
|
||||
.padding(8.dp)
|
||||
) {
|
||||
LazyRow {
|
||||
attachmentItems.forEach { attachmentItem ->
|
||||
item {
|
||||
Image(
|
||||
painter = attachmentItem.painter,
|
||||
contentDescription = "attachment",
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.aspectRatio(1.0f)
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
)
|
||||
|
||||
Spacer(Modifier.width(4.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(Modifier.weight(1f))
|
||||
|
||||
ElevatedButton(
|
||||
onClick = onClear,
|
||||
colors = ButtonDefaults.elevatedButtonColors(
|
||||
containerColor = MaterialTheme.colors.background
|
||||
),
|
||||
modifier = Modifier.align(Alignment.CenterVertically)
|
||||
) {
|
||||
Text("Remove")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MessageEntry(
|
||||
keyboardType: KeyboardType = KeyboardType.Text,
|
||||
onTextChanged: (TextFieldValue) -> Unit,
|
||||
textFieldValue: TextFieldValue,
|
||||
attachmentItems: List<AttachmentRowItem> = listOf(),
|
||||
|
||||
onAddAttachment: () -> Unit = {},
|
||||
onClearAttachments: () -> Unit = {},
|
||||
onTextChanged: (TextFieldValue) -> Unit,
|
||||
onSend: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colors.onSurface.copy(alpha = 0.18f))
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp, horizontal = 12.dp)
|
||||
.imePadding()
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
Column {
|
||||
if (attachmentItems.isNotEmpty()) {
|
||||
AttachmentRow(attachmentItems, onClear = onClearAttachments)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colors.onSurface.copy(alpha = 0.18f))
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp, horizontal = 4.dp)
|
||||
.imePadding()
|
||||
.navigationBarsPadding()
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onAddAttachment,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.attach_file),
|
||||
contentDescription = "Attach File"
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(8.dp))
|
||||
|
||||
Surface(
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
modifier = Modifier.weight(1f)
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
.shadow(4.dp)
|
||||
.shadow(3.dp)
|
||||
) {
|
||||
BasicTextField(
|
||||
value = textFieldValue,
|
||||
@@ -68,8 +153,14 @@ fun MessageEntry(
|
||||
|
||||
Spacer(Modifier.width(8.dp))
|
||||
|
||||
Button(onClick = onSend) {
|
||||
Button(
|
||||
onClick = onSend,
|
||||
enabled = (attachmentItems.isNotEmpty() || textFieldValue.text.isNotEmpty())
|
||||
) {
|
||||
Text(text = "Send")
|
||||
}
|
||||
|
||||
Spacer(Modifier.width(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package net.buzzert.kordophonedroid.ui.messagelist
|
||||
|
||||
import android.util.Log
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -28,8 +30,7 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCompositionContext
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -46,6 +47,7 @@ import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
import coil.request.ImageRequest
|
||||
import net.buzzert.kordophone.backend.model.GUID
|
||||
import net.buzzert.kordophonedroid.ui.Destination
|
||||
@@ -129,6 +131,13 @@ fun MessageListScreen(
|
||||
}
|
||||
}
|
||||
|
||||
var attachmentUris by remember { mutableStateOf<Set<Uri>>(mutableSetOf()) }
|
||||
val imagePicker = rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { uri: Uri? ->
|
||||
uri?.let {
|
||||
attachmentUris = attachmentUris.plus(it)
|
||||
}
|
||||
}
|
||||
|
||||
val navController = LocalNavController.current
|
||||
Scaffold(
|
||||
topBar = {
|
||||
@@ -138,8 +147,18 @@ fun MessageListScreen(
|
||||
messages = messageItems,
|
||||
paddingValues = padding,
|
||||
showSenders = viewModel.isGroupChat,
|
||||
attachmentUris = attachmentUris,
|
||||
onAddAttachment = {
|
||||
imagePicker.launch("image/*")
|
||||
},
|
||||
onClearAttachments = {
|
||||
attachmentUris = setOf()
|
||||
},
|
||||
onSendMessage = { text ->
|
||||
viewModel.enqueueOutgoingMessage(text)
|
||||
viewModel.enqueueOutgoingMessage(
|
||||
text = text,
|
||||
attachmentUris = attachmentUris
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -150,6 +169,9 @@ fun MessageTranscript(
|
||||
messages: List<MessageListItem>,
|
||||
paddingValues: PaddingValues,
|
||||
showSenders: Boolean,
|
||||
attachmentUris: Set<Uri>,
|
||||
onAddAttachment: () -> Unit,
|
||||
onClearAttachments: () -> Unit,
|
||||
onSendMessage: (text: String) -> Unit,
|
||||
) {
|
||||
val scrollState = rememberLazyListState()
|
||||
@@ -157,6 +179,13 @@ fun MessageTranscript(
|
||||
mutableStateOf(TextFieldValue())
|
||||
}
|
||||
|
||||
val attachmentRowItems = attachmentUris.map {
|
||||
AttachmentRowItem(
|
||||
painter = rememberAsyncImagePainter(model = it),
|
||||
id = "attachmentID"
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
@@ -172,6 +201,9 @@ fun MessageTranscript(
|
||||
MessageEntry(
|
||||
onTextChanged = { textState = it },
|
||||
textFieldValue = textState,
|
||||
attachmentItems = attachmentRowItems,
|
||||
onAddAttachment = onAddAttachment,
|
||||
onClearAttachments = onClearAttachments,
|
||||
onSend = {
|
||||
onSendMessage(textState.text)
|
||||
|
||||
@@ -198,7 +230,6 @@ fun Messages(
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
var lastDate: Date = Date()
|
||||
val dateFormatter = SimpleDateFormat.getDateTimeInstance()
|
||||
|
||||
for (index in messages.indices) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.buzzert.kordophonedroid.ui.messagelist
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -97,7 +98,14 @@ class MessageListViewModel @Inject constructor(
|
||||
|
||||
val isGroupChat: Boolean get() = conversation!!.isGroupChat
|
||||
|
||||
fun enqueueOutgoingMessage(text: String) {
|
||||
fun enqueueOutgoingMessage(
|
||||
text: String,
|
||||
attachmentUris: Set<Uri>
|
||||
) {
|
||||
// TODO: Handle attachments!
|
||||
// Probably make a special OutgoingMessage object for this, since a lot of Message fields are
|
||||
// meaningless here. We don't have GUIDs yet either.
|
||||
|
||||
val outgoingMessage = Message(
|
||||
guid = UUID.randomUUID().toString(),
|
||||
text = text,
|
||||
|
||||
10
app/src/main/res/drawable/attach_file.xml
Normal file
10
app/src/main/res/drawable/attach_file.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M720,630Q720,734 647,807Q574,880 470,880Q366,880 293,807Q220,734 220,630L220,260Q220,185 272.5,132.5Q325,80 400,80Q475,80 527.5,132.5Q580,185 580,260L580,610Q580,656 548,688Q516,720 470,720Q424,720 392,688Q360,656 360,610L360,240L440,240L440,610Q440,623 448.5,631.5Q457,640 470,640Q483,640 491.5,631.5Q500,623 500,610L500,260Q499,218 470.5,189Q442,160 400,160Q358,160 329,189Q300,218 300,260L300,630Q299,701 349,750.5Q399,800 470,800Q540,800 589,750.5Q638,701 640,630L640,240L720,240L720,630Z"/>
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/sedona.jpeg
Normal file
BIN
app/src/main/res/drawable/sedona.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 659 KiB |
Reference in New Issue
Block a user