Private
Public Access
1
0

app: MessageEntry: Adds UI support for uploading attachments

Still need backend support to finish this.
This commit is contained in:
2024-04-04 23:52:17 -07:00
parent b160baae3e
commit b5eccbd000
6 changed files with 184 additions and 22 deletions

View File

@@ -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

View File

@@ -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))
}
}
}

View File

@@ -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) {

View File

@@ -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,

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 659 KiB