session-android/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt

365 lines
12 KiB
Kotlin
Raw Normal View History

2021-07-13 08:22:10 +02:00
package org.thoughtcrime.securesms.conversation.v2
2023-06-30 02:18:48 +02:00
import android.content.Intent
2021-07-13 08:22:10 +02:00
import android.os.Bundle
2023-06-30 15:19:23 +02:00
import androidx.compose.foundation.ExperimentalFoundationApi
2023-07-04 02:01:14 +02:00
import androidx.compose.foundation.clickable
2023-06-29 04:07:55 +02:00
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
2023-07-04 07:41:40 +02:00
import androidx.compose.foundation.layout.RowScope
2023-07-03 03:05:50 +02:00
import androidx.compose.foundation.layout.aspectRatio
2023-06-29 04:07:55 +02:00
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
2023-06-30 15:19:23 +02:00
import androidx.compose.foundation.pager.HorizontalPager
2023-07-03 09:49:33 +02:00
import androidx.compose.foundation.pager.PagerState
2023-07-03 03:05:50 +02:00
import androidx.compose.foundation.pager.rememberPagerState
2023-06-29 04:07:55 +02:00
import androidx.compose.foundation.rememberScrollState
2023-07-03 13:55:59 +02:00
import androidx.compose.foundation.shape.CircleShape
2023-06-29 04:07:55 +02:00
import androidx.compose.foundation.verticalScroll
2023-07-03 13:55:59 +02:00
import androidx.compose.material.Icon
2023-06-29 11:44:47 +02:00
import androidx.compose.material.LocalTextStyle
2023-07-03 13:55:59 +02:00
import androidx.compose.material.Surface
2023-06-29 04:07:55 +02:00
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
2023-06-29 14:41:15 +02:00
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
2023-06-29 04:07:55 +02:00
import androidx.compose.ui.Modifier
2023-07-01 10:08:58 +02:00
import androidx.compose.ui.layout.ContentScale
2023-06-29 04:07:55 +02:00
import androidx.compose.ui.platform.ComposeView
2023-07-03 13:55:59 +02:00
import androidx.compose.ui.res.painterResource
2023-06-29 11:44:47 +02:00
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
2023-06-29 04:07:55 +02:00
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
2023-06-30 03:31:57 +02:00
import androidx.compose.ui.viewinterop.AndroidView
2023-07-01 10:08:58 +02:00
import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi
import com.bumptech.glide.integration.compose.GlideImage
import dagger.hilt.android.AndroidEntryPoint
2021-07-13 08:22:10 +02:00
import network.loki.messenger.R
2023-06-30 04:05:22 +02:00
import org.session.libsession.utilities.recipients.Recipient
2023-07-04 02:01:14 +02:00
import org.thoughtcrime.securesms.MediaPreviewActivity
2021-07-13 08:22:10 +02:00
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.components.ProfilePictureView
import org.thoughtcrime.securesms.database.Storage
2023-06-30 02:18:48 +02:00
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
2023-07-01 10:08:58 +02:00
import org.thoughtcrime.securesms.mms.Slide
2023-06-29 09:41:11 +02:00
import org.thoughtcrime.securesms.ui.AppTheme
2023-07-03 11:00:15 +02:00
import org.thoughtcrime.securesms.ui.CarouselNextButton
import org.thoughtcrime.securesms.ui.CarouselPrevButton
2023-06-29 09:41:11 +02:00
import org.thoughtcrime.securesms.ui.Cell
2023-07-03 05:30:11 +02:00
import org.thoughtcrime.securesms.ui.CellNoMargin
import org.thoughtcrime.securesms.ui.CellWithPaddingAndMargin
2023-07-04 04:04:20 +02:00
import org.thoughtcrime.securesms.ui.Divider
2023-07-04 06:39:40 +02:00
import org.thoughtcrime.securesms.ui.HorizontalPagerIndicator
2023-06-29 09:41:11 +02:00
import org.thoughtcrime.securesms.ui.ItemButton
2023-07-04 06:39:40 +02:00
import org.thoughtcrime.securesms.ui.blackAlpha40
2023-06-30 05:55:16 +02:00
import org.thoughtcrime.securesms.ui.colorDestructive
2023-06-29 09:41:11 +02:00
import org.thoughtcrime.securesms.ui.destructiveButtonColors
import javax.inject.Inject
2021-07-13 08:22:10 +02:00
2023-06-29 09:41:11 +02:00
@AndroidEntryPoint
2023-07-03 09:49:33 +02:00
class MessageDetailActivity : PassphraseRequiredActionBarActivity() {
2023-06-30 02:18:48 +02:00
private var timestamp: Long = 0L
@Inject
lateinit var storage: Storage
2021-07-13 08:22:10 +02:00
companion object {
// Extras
const val MESSAGE_TIMESTAMP = "message_timestamp"
2023-06-30 02:18:48 +02:00
const val ON_REPLY = 1
const val ON_RESEND = 2
const val ON_DELETE = 3
}
val viewModel = MessageDetailsViewModel()
2021-07-13 08:22:10 +02:00
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
2023-06-29 04:07:55 +02:00
2023-06-30 02:18:48 +02:00
timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L)
2023-07-04 07:16:56 +02:00
val messageRecord =
2023-07-03 09:49:33 +02:00
DatabaseComponent.get(this).mmsSmsDatabase().getMessageForTimestamp(timestamp) ?: run {
finish()
return
}
2023-06-30 02:18:48 +02:00
2023-07-03 09:49:33 +02:00
val error = DatabaseComponent.get(this).lokiMessageDatabase()
2023-07-04 07:16:56 +02:00
.getErrorMessage(messageRecord.getId())
2023-06-30 05:55:16 +02:00
viewModel.setMessageRecord(messageRecord, error)
2023-06-29 04:07:55 +02:00
title = resources.getString(R.string.conversation_context__menu_message_details)
2023-07-04 07:16:56 +02:00
ComposeView(this)
.apply { setContent { MessageDetailsScreen() } }
.let(::setContentView)
2023-06-29 04:07:55 +02:00
}
2023-06-29 14:41:15 +02:00
@Composable
2023-06-30 02:18:48 +02:00
private fun MessageDetailsScreen() {
2023-06-29 14:41:15 +02:00
val details by viewModel.details.observeAsState(MessageDetails())
2023-06-30 02:18:48 +02:00
MessageDetails(
details,
onReply = { setResultAndFinish(ON_REPLY) },
onResend = { setResultAndFinish(ON_RESEND) },
2023-07-04 02:01:14 +02:00
onDelete = { setResultAndFinish(ON_DELETE) },
onClickImage = { slide ->
MediaPreviewActivity.getPreviewIntent(this, slide, details.mmsRecord, details.sender)
.let(::startActivity)
}
2023-06-30 02:18:48 +02:00
)
}
private fun setResultAndFinish(code: Int) {
Bundle().apply { putLong(MESSAGE_TIMESTAMP, timestamp) }
.let(Intent()::putExtras)
2023-06-30 15:19:23 +02:00
.let { setResult(code, it) }
2023-06-30 02:18:48 +02:00
finish()
2023-06-29 04:07:55 +02:00
}
@Preview
@Composable
2023-06-29 14:41:15 +02:00
fun PreviewMessageDetails() {
MessageDetails(
2023-06-30 05:55:16 +02:00
MessageDetails(
2023-07-01 10:08:58 +02:00
attachments = listOf(),
2023-06-30 05:55:16 +02:00
sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"),
received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"),
error = TitledText("Error:", "Message failed to send"),
2023-07-01 10:08:58 +02:00
senderInfo = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg"),
2023-06-30 05:55:16 +02:00
)
2023-06-29 04:07:55 +02:00
)
2023-06-29 14:41:15 +02:00
}
2023-06-29 04:07:55 +02:00
2023-06-29 14:41:15 +02:00
@Composable
2023-06-30 02:18:48 +02:00
fun MessageDetails(
messageDetails: MessageDetails,
onReply: () -> Unit = {},
onResend: () -> Unit = {},
onDelete: () -> Unit = {},
2023-07-04 02:01:14 +02:00
onClickImage: (Slide) -> Unit = {},
2023-06-30 02:18:48 +02:00
) {
2023-06-29 14:41:15 +02:00
messageDetails.apply {
AppTheme {
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
2023-07-04 02:01:14 +02:00
Attachments(attachments) { onClickImage(it) }
2023-07-04 07:16:56 +02:00
MetaDataCell(messageDetails)
2023-07-03 13:55:59 +02:00
Buttons(
2023-07-04 07:16:56 +02:00
error != null,
2023-07-03 13:55:59 +02:00
onReply,
onResend,
onDelete,
)
}
}
}
}
2023-07-04 07:16:56 +02:00
@Composable
fun MetaDataCell(
messageDetails: MessageDetails,
) {
2023-07-04 07:41:40 +02:00
messageDetails.apply {
if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
sent?.let { TitledText(it) }
received?.let { TitledText(it) }
error?.let { TitledErrorText(it) }
senderInfo?.let {
TitledView("From:") {
Row {
sender?.let { Avatar(it) }
TitledMonospaceText(it)
2023-07-04 07:16:56 +02:00
}
}
}
}
}
}
}
2023-07-04 07:41:40 +02:00
@Composable
fun RowScope.Avatar(sender: Recipient) {
Box(
modifier = Modifier
.width(60.dp)
.align(Alignment.CenterVertically)
) {
AndroidView(
factory = {
ProfilePictureView(it).apply { update(sender) }
},
modifier = Modifier.width(46.dp).height(46.dp)
)
}
}
2023-07-03 13:55:59 +02:00
@Composable
fun Buttons(
hasError: Boolean,
onReply: () -> Unit = {},
onResend: () -> Unit = {},
onDelete: () -> Unit = {},
) {
Cell {
Column {
ItemButton(
"Reply",
R.drawable.ic_message_details__reply,
onClick = onReply
)
Divider()
if (hasError) {
ItemButton(
"Resend",
R.drawable.ic_message_details__refresh,
onClick = onResend
)
Divider()
2023-06-29 04:07:55 +02:00
}
2023-07-03 13:55:59 +02:00
ItemButton(
"Delete",
R.drawable.ic_message_details__trash,
colors = destructiveButtonColors(),
onClick = onDelete
)
2023-06-29 04:07:55 +02:00
}
2021-07-13 08:22:10 +02:00
}
}
2023-07-03 03:05:50 +02:00
@Composable
2023-07-04 02:01:14 +02:00
fun Attachments(attachments: List<Attachment>, onClick: (Slide) -> Unit) {
2023-07-03 03:05:50 +02:00
val slide = attachments.firstOrNull()?.slide ?: return
when {
2023-07-04 07:16:56 +02:00
slide.hasImage() -> Carousel(attachments, onClick)
2023-07-03 03:05:50 +02:00
}
}
2023-07-04 06:39:40 +02:00
@OptIn(ExperimentalFoundationApi::class,)
2023-07-03 03:05:50 +02:00
@Composable
2023-07-04 07:16:56 +02:00
fun Carousel(attachments: List<Attachment>, onClick: (Slide) -> Unit) {
2023-07-03 05:30:11 +02:00
val imageAttachments = attachments.filter { it.slide.hasImage() }
2023-07-03 09:49:33 +02:00
val pagerState = rememberPagerState { imageAttachments.size }
2023-07-03 03:05:50 +02:00
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
2023-07-03 09:49:33 +02:00
Row {
2023-07-04 05:56:26 +02:00
CarouselPrevButton(pagerState)
2023-07-03 09:49:33 +02:00
Box(modifier = Modifier.weight(1f)) {
2023-07-04 07:16:56 +02:00
CellPager(pagerState, imageAttachments, onClick)
2023-07-04 06:39:40 +02:00
HorizontalPagerIndicator(pagerState)
ExpandButton(modifier = Modifier.align(Alignment.BottomEnd).padding(8.dp))
2023-07-03 03:05:50 +02:00
}
2023-07-04 05:56:26 +02:00
CarouselNextButton(pagerState)
2023-07-03 03:05:50 +02:00
}
2023-07-03 09:49:33 +02:00
FileDetails(attachments, pagerState)
}
}
2023-07-04 06:39:40 +02:00
@OptIn(
ExperimentalFoundationApi::class,
ExperimentalGlideComposeApi::class
)
@Composable
private fun CellPager(pagerState: PagerState, imageAttachments: List<Attachment>, onClick: (Slide) -> Unit) {
CellNoMargin {
HorizontalPager(state = pagerState) { i ->
val slide = imageAttachments[i].slide
GlideImage(
contentScale = ContentScale.Crop,
modifier = Modifier
.aspectRatio(1f)
.clickable { onClick(slide) },
model = slide.uri,
contentDescription = slide.fileName.orNull() ?: "image"
)
}
}
}
2023-07-03 09:49:33 +02:00
2023-07-04 06:39:40 +02:00
@Composable
fun ExpandButton(modifier: Modifier) {
Surface(
shape = CircleShape,
color = blackAlpha40,
modifier = modifier
) {
Icon(
painter = painterResource(id = R.drawable.ic_expand),
contentDescription = ""
)
}
}
2023-07-03 09:49:33 +02:00
@OptIn(ExperimentalFoundationApi::class, ExperimentalLayoutApi::class)
@Composable
fun FileDetails(attachments: List<Attachment>, pagerState: PagerState) {
attachments[pagerState.currentPage].fileDetails.takeIf { it.isNotEmpty() }?.let {
CellWithPaddingAndMargin {
FlowRow(
verticalArrangement = Arrangement.spacedBy(16.dp),
maxItemsInEachRow = 2
) {
2023-07-04 07:16:56 +02:00
it.forEach { TitledText(it, Modifier.weight(1f)) }
2023-07-03 03:05:50 +02:00
}
}
}
}
2023-07-04 07:41:40 +02:00
@Composable
fun TitledErrorText(titledText: TitledText, modifier: Modifier = Modifier) {
TitledText(
titledText,
modifier = modifier,
valueStyle = LocalTextStyle.current.copy(color = colorDestructive))
}
@Composable
fun TitledMonospaceText(titledText: TitledText, modifier: Modifier = Modifier) {
TitledText(
titledText,
modifier = modifier,
valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace))
}
2023-06-29 04:07:55 +02:00
@Composable
2023-07-04 07:16:56 +02:00
fun TitledText(
2023-07-03 09:49:33 +02:00
titledText: TitledText,
modifier: Modifier = Modifier,
valueStyle: TextStyle = LocalTextStyle.current
) {
2023-06-29 04:07:55 +02:00
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) {
Title(titledText.title)
2023-06-29 11:44:47 +02:00
Text(titledText.value, style = valueStyle)
2023-06-29 04:07:55 +02:00
}
}
2023-06-29 09:41:11 +02:00
2023-06-29 04:07:55 +02:00
@Composable
2023-07-04 07:16:56 +02:00
fun TitledView(title: String, modifier: Modifier = Modifier, content: @Composable () -> Unit) {
2023-06-29 04:07:55 +02:00
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) {
Title(title)
content()
2021-07-13 08:22:10 +02:00
}
}
2023-06-29 04:07:55 +02:00
@Composable
fun Title(text: String) {
Text(text, fontWeight = FontWeight.Bold)
}
2023-06-29 09:41:11 +02:00
}