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

457 lines
18 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-07-03 09:49:33 +02:00
import androidx.annotation.DrawableRes
2023-06-30 15:19:23 +02:00
import androidx.compose.foundation.ExperimentalFoundationApi
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-03 09:49:33 +02:00
import androidx.compose.foundation.layout.Spacer
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
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Divider
2023-07-03 09:49:33 +02:00
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
2023-06-29 11:44:47 +02:00
import androidx.compose.material.LocalTextStyle
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
2023-07-03 09:49:33 +02:00
import androidx.compose.runtime.rememberCoroutineScope
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 09:49:33 +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-06-29 14:41:15 +02:00
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
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
2023-07-03 09:49:33 +02:00
import kotlinx.coroutines.launch
2021-07-13 08:22:10 +02:00
import network.loki.messenger.R
2023-07-01 10:08:58 +02:00
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.utilities.Util
2023-06-30 04:05:22 +02:00
import org.session.libsession.utilities.recipients.Recipient
2021-07-13 08:22:10 +02:00
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.components.ProfilePictureView
2023-07-01 10:08:58 +02:00
import org.thoughtcrime.securesms.database.AttachmentDatabase
import org.thoughtcrime.securesms.database.Storage
2021-07-13 08:22:10 +02:00
import org.thoughtcrime.securesms.database.model.MessageRecord
2023-06-30 15:19:23 +02:00
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
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
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-06-29 09:41:11 +02:00
import org.thoughtcrime.securesms.ui.ItemButton
import org.thoughtcrime.securesms.ui.LocalExtraColors
2023-07-03 05:30:11 +02:00
import org.thoughtcrime.securesms.ui.SessionHorizontalPagerIndicator
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
Paged conversation recycler, update compile sdk version 31 (#1049) * Update build tools * Update appcompat version * Update dependencies * feat: add paging into conversation recycler and queries to fetch data off-thread * refactor: wip for updating paged results and bucketing messages / fetching enough to display * fix: currently works for scrolling and possibly refreshing? need scroll to message and auto scroll down on insert (at bottom) * fix: search and scrolling to X message works now * build: increase version code and name * fix: re-add refresh, remove the outdated comment * refactor: lets see if 25 size pages increases performance :eyes: * feat: add in some equals overrides for mms records to refresh if media has finished DLing * feat: add scroll to bottom for new messages if we are at the end of the chat * build: update build numbers * fix: update AGP and fix compile errors for sdk version 31 * feat: add log for loki-avatar and loki-fs on upload types and responses * feat: increase build number to match latest installed version * feat: changing props and permission checks for call service * fix: possible service exception when no call ID remote foreground service not terminated * revert: google services version * fix: re-add paging dependency * feat: adding new last seen function and figuring out the last seen for recycler adapter * build: update version names and codes for deploy * refactor: undo the new adapter and query changes to use previous cursor logic. revert this commit to enable new paged adapter * fix: use author's address in typist equality and hashcode for set inclusion * refactor: refactor the select contacts activity * refactor: refactor the select contacts activity * build: update version code * fix: hide all other bound views if deleted * refactor: change voice message tint, upgrade build number * fix: message detail showing up properly * revert: realise copy public key is actually not allowed if open group participant * fix: copy session ID, message detail activity support re-enabled * build: update build version code * build: remove version name * build: update build code * feat: google services version minimum compatible * fix: selection for re-created objects not properly highlighting * fix: foreground CENTER_INSIDE instead of just CENTER for scaletype * build: update version code * fix: don't show error if no error * build: update version code * fix: clear error messages if any on successful send Co-authored-by: charles <charles@oxen.io>
2022-12-19 01:29:05 +01:00
import java.util.*
2023-07-01 10:08:58 +02:00
import java.util.concurrent.TimeUnit
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
2021-07-13 08:22:10 +02:00
var messageRecord: MessageRecord? = null
@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()
2023-07-03 09:49:33 +02:00
class MessageDetailsViewModel : ViewModel() {
2023-07-01 10:08:58 +02:00
@Inject
lateinit var attachmentDb: AttachmentDatabase
2023-06-30 02:18:48 +02:00
2023-06-30 05:55:16 +02:00
fun setMessageRecord(value: MessageRecord?, error: String?) {
2023-06-30 15:19:23 +02:00
val mmsRecord = value as? MmsMessageRecord
2023-07-01 10:08:58 +02:00
val slides: List<Slide> = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList()
2023-06-30 15:19:23 +02:00
2023-06-30 02:18:48 +02:00
_details.value = value?.run {
MessageDetails(
2023-07-01 10:08:58 +02:00
attachments = slides.map { slide ->
val duration = slide.takeIf { it.hasAudio() }
?.let { it.asAttachment() as? DatabaseAttachment }
?.let { attachment ->
2023-07-03 09:49:33 +02:00
attachmentDb.getAttachmentAudioExtras(attachment.attachmentId)
?.let { audioExtras ->
audioExtras.durationMs.takeIf { it > 0 }?.let {
String.format(
"%01d:%02d",
TimeUnit.MILLISECONDS.toMinutes(it),
TimeUnit.MILLISECONDS.toSeconds(it) % 60
)
}
2023-07-01 10:08:58 +02:00
}
}
2023-07-03 09:49:33 +02:00
val details = slide.run {
listOfNotNull(
fileName.orNull()?.let { TitledText("File Id:", it) },
TitledText("File Type:", asAttachment().contentType),
TitledText("File Size:", Util.getPrettyFileSize(fileSize)),
if (slide.hasImage()) {
TitledText(
"Resolution:",
slide.asAttachment().run { "${width}x$height" })
} else null,
duration?.let { TitledText("Duration:", it) },
)
}
Attachment(slide, details)
2023-07-01 10:08:58 +02:00
},
2023-06-30 02:18:48 +02:00
sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) },
2023-07-03 09:49:33 +02:00
received = dateReceived.let(::Date).toString()
.let { TitledText("Received:", it) },
2023-06-30 05:55:16 +02:00
error = error?.let { TitledText("Error:", it) },
2023-07-03 09:49:33 +02:00
senderInfo = individualRecipient.run {
name?.let {
TitledText(
it,
address.serialize()
)
}
},
2023-06-30 04:05:22 +02:00
sender = individualRecipient
2023-06-30 02:18:48 +02:00
)
2023-07-01 10:08:58 +02:00
}
2023-06-30 02:18:48 +02:00
}
private var _details = MutableLiveData(MessageDetails())
val details: LiveData<MessageDetails> = _details
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-03 09:49:33 +02:00
messageRecord =
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()
.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-06-29 14:41:15 +02:00
setContentView(ComposeView(this).apply {
setContent {
MessageDetailsScreen()
}
})
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) },
onDelete = { setResultAndFinish(ON_DELETE) }
)
}
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
}
data class TitledText(val title: String, val value: String)
2023-06-29 14:41:15 +02:00
data class MessageDetails(
2023-07-01 10:08:58 +02:00
val attachments: List<Attachment> = emptyList(),
2023-06-29 14:41:15 +02:00
val sent: TitledText? = null,
val received: TitledText? = null,
2023-06-30 05:55:16 +02:00
val error: TitledText? = null,
2023-06-30 04:05:22 +02:00
val senderInfo: TitledText? = null,
val sender: Recipient? = null
2023-06-29 14:41:15 +02:00
)
2023-07-01 10:08:58 +02:00
data class Attachment(
val slide: Slide,
val fileDetails: List<TitledText>
)
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-06-29 14:41:15 +02:00
messageDetails.apply {
AppTheme {
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
2023-07-03 03:05:50 +02:00
Attachments(attachments)
2023-07-03 05:30:11 +02:00
if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin {
2023-06-29 14:41:15 +02:00
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
sent?.let { titledText(it) }
received?.let { titledText(it) }
2023-07-03 09:49:33 +02:00
error?.let {
titledText(
it,
valueStyle = LocalTextStyle.current.copy(color = colorDestructive)
)
}
2023-06-30 04:05:22 +02:00
senderInfo?.let {
2023-06-29 14:41:15 +02:00
titledView("From:") {
Row {
2023-06-30 05:55:16 +02:00
sender?.let {
2023-07-03 09:49:33 +02:00
Box(
modifier = Modifier
.width(60.dp)
.align(Alignment.CenterVertically)
) {
2023-06-30 05:55:16 +02:00
AndroidView(
2023-07-03 09:49:33 +02:00
factory = {
ProfilePictureView(it).apply {
update(
sender
)
}
},
2023-06-30 05:55:16 +02:00
modifier = Modifier
.width(46.dp)
.height(46.dp)
)
}
2023-06-30 03:31:57 +02:00
}
2023-06-29 14:41:15 +02:00
Column {
2023-07-03 09:49:33 +02:00
titledText(
it,
valueStyle = LocalTextStyle.current.copy(fontFamily = FontFamily.Monospace)
)
2023-06-29 14:41:15 +02:00
}
}
2023-07-03 09:49:33 +02:00
}
2023-06-29 04:07:55 +02:00
}
}
}
2023-06-29 14:41:15 +02:00
Cell {
Column {
2023-07-03 09:49:33 +02:00
ItemButton(
"Reply",
R.drawable.ic_message_details__reply,
onClick = onReply
)
2023-06-29 14:41:15 +02:00
Divider()
2023-06-30 05:55:16 +02:00
if (error != null) {
2023-07-03 09:49:33 +02:00
ItemButton(
"Resend",
R.drawable.ic_message_details__refresh,
onClick = onResend
)
2023-06-30 05:55:16 +02:00
Divider()
}
2023-07-03 09:49:33 +02:00
ItemButton(
"Delete",
R.drawable.ic_message_details__trash,
colors = destructiveButtonColors(),
onClick = onDelete
)
2023-06-29 14:41:15 +02:00
}
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
fun Attachments(attachments: List<Attachment>) {
val slide = attachments.firstOrNull()?.slide ?: return
when {
slide.hasImage() -> ImageAttachments(attachments)
}
}
@OptIn(
ExperimentalFoundationApi::class,
ExperimentalGlideComposeApi::class,
)
@Composable
fun ImageAttachments(attachments: List<Attachment>) {
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 {
if (imageAttachments.size >= 2) PrevButton(pagerState, modifier = Modifier.align(Alignment.CenterVertically))
else Spacer(modifier = Modifier.width(32.dp))
Box(modifier = Modifier.weight(1f)) {
CellNoMargin {
HorizontalPager(state = pagerState) { i ->
imageAttachments[i].slide.apply {
GlideImage(
contentScale = ContentScale.Crop,
modifier = Modifier.aspectRatio(1f),
model = uri,
contentDescription = fileName.orNull() ?: "image"
)
}
2023-07-03 05:30:11 +02:00
}
}
if (imageAttachments.size >= 2) {
SessionHorizontalPagerIndicator(
modifier = Modifier.align(Alignment.BottomCenter),
pagerState = pagerState,
pageCount = imageAttachments.size,
2023-07-03 03:05:50 +02:00
)
}
}
2023-07-03 09:49:33 +02:00
if (imageAttachments.size >= 2) NextButton(pagerState, modifier = Modifier.align(Alignment.CenterVertically))
else Spacer(modifier = Modifier.width(32.dp))
2023-07-03 03:05:50 +02:00
}
2023-07-03 09:49:33 +02:00
FileDetails(attachments, pagerState)
}
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun PrevButton(pagerState: PagerState, modifier: Modifier = Modifier) {
CarouselButton(pagerState, modifier = modifier, enabled = pagerState.canScrollBackward, id = R.drawable.ic_prev, delta = -1)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun NextButton(pagerState: PagerState, modifier: Modifier = Modifier) {
CarouselButton(pagerState, modifier = modifier, enabled = pagerState.canScrollForward, id = R.drawable.ic_next, delta = 1)
}
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun CarouselButton(
pagerState: PagerState,
modifier: Modifier = Modifier,
enabled: Boolean,
@DrawableRes id: Int,
delta: Int
) {
val animationScope = rememberCoroutineScope()
pagerState.apply {
IconButton(
modifier = Modifier
.width(40.dp)
.then(modifier),
enabled = enabled,
onClick = { animationScope.launch { animateScrollToPage(currentPage + delta) } }) {
Icon(
painter = painterResource(id = id),
contentDescription = "",
)
}
}
}
@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
) {
it.forEach { titledText(it, Modifier.weight(1f)) }
2023-07-03 03:05:50 +02:00
}
}
}
}
2023-06-29 04:07:55 +02:00
@Composable
fun Divider() {
2023-07-03 09:49:33 +02:00
Divider(
modifier = Modifier.padding(horizontal = 16.dp),
thickness = 1.dp,
color = LocalExtraColors.current.divider
)
2023-06-29 04:07:55 +02:00
}
@Composable
2023-07-03 09:49:33 +02:00
fun titledText(
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
fun titledView(title: String, modifier: Modifier = Modifier, content: @Composable () -> Unit) {
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
}