Create Message Details screen

This commit is contained in:
andrew 2023-06-29 11:37:55 +09:30
parent ab8b2c42b9
commit fc108b34db
2 changed files with 166 additions and 61 deletions

View File

@ -28,6 +28,7 @@ configurations.all {
}
dependencies {
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation "com.google.android.material:material:$materialVersion"
@ -161,8 +162,11 @@ dependencies {
testImplementation 'org.robolectric:shadows-multidex:4.4'
implementation 'androidx.compose.ui:ui:1.4.3'
implementation 'androidx.compose.material:material:1.4.3'
implementation 'androidx.compose.ui:ui-tooling:1.4.3'
implementation "com.google.accompanist:accompanist-themeadapter-appcompat:0.30.1"
implementation 'androidx.compose.foundation:foundation-layout:1.5.0-alpha02'
implementation 'androidx.compose.material:material:1.5.0-alpha02'
}
def canonicalVersionCode = 338

View File

@ -2,32 +2,49 @@ package org.thoughtcrime.securesms.conversation.v2
import android.os.Bundle
import android.view.View
import androidx.core.view.isVisible
import androidx.annotation.DrawableRes
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
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Card
import androidx.compose.material.Divider
import androidx.compose.material.Icon
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
import dagger.hilt.android.AndroidEntryPoint
import network.loki.messenger.R
import network.loki.messenger.databinding.ActivityMessageDetailBinding
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.messaging.utilities.SodiumUtilities
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.Address
import org.session.libsession.utilities.ExpirationUtil
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.IdPrefix
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities
import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.util.DateUtils
import java.text.SimpleDateFormat
import java.util.*
import javax.inject.Inject
@AndroidEntryPoint
class MessageDetailActivity: PassphraseRequiredActionBarActivity() {
private lateinit var binding: ActivityMessageDetailBinding
var messageRecord: MessageRecord? = null
@Inject
@ -42,58 +59,142 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
binding = ActivityMessageDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
title = resources.getString(R.string.conversation_context__menu_message_details)
val timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L)
// We only show this screen for messages fail to send,
// so the author of the messages must be the current user.
val author = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!)
messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author) ?: run {
finish()
return
}
val threadId = messageRecord!!.threadId
val openGroup = storage.getOpenGroup(threadId)
val blindedKey = openGroup?.let { group ->
val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return@let null
val blindingEnabled = storage.getServerCapabilities(group.server).contains(OpenGroupApi.Capability.BLIND.name.lowercase())
if (blindingEnabled) {
SodiumUtilities.blindedKeyPair(group.publicKey, userEdKeyPair)?.publicKey?.asBytes
?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString
} else null
}
updateContent()
binding.resendButton.setOnClickListener {
ResendMessageUtilities.resend(this, messageRecord!!, blindedKey)
finish()
title = resources.getString(R.string.conversation_context__menu_message_details)
setContentView(createComposeView())
}
private fun createComposeView(): ComposeView = ComposeView(this).apply {
id = View.generateViewId()
setContent {
MessageDetails()
}
}
fun updateContent() {
val dateLocale = Locale.getDefault()
val dateFormatter: SimpleDateFormat = DateUtils.getDetailedDateFormatter(this, dateLocale)
binding.sentTime.text = dateFormatter.format(Date(messageRecord!!.dateSent))
data class TitledText(val title: String, val value: String)
val errorMessage = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId())
if (errorMessage != null) {
binding.errorMessage.text = errorMessage
binding.resendContainer.isVisible = true
binding.errorContainer.isVisible = true
} else {
binding.errorContainer.isVisible = false
binding.resendContainer.isVisible = false
@OptIn(ExperimentalLayoutApi::class)
@Preview
@Composable
fun MessageDetails() {
val fileDetails = listOf(
TitledText("File Id:", "1237896548514214124235985214"),
TitledText("File Type:", ".PNG"),
TitledText("File Size:", "6mb"),
TitledText("Resolution:", "550x550"),
TitledText("Duration:", "N/A"),
)
val sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022 ")
val received = TitledText("Received:", "6:12 AM Tue, 09/08/2022 ")
val user = TitledText("Connor", "d4f1g54sdf5g1d5f4g65ds4564df65f4g65d54gdfsg")
AppCompatTheme {
Column(
modifier = Modifier.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(16.dp)) {
CardWithPadding {
FlowRow(
verticalArrangement = Arrangement.spacedBy(16.dp),
maxItemsInEachRow = 2
) {
fileDetails.forEach {
titledText(it, Modifier.weight(1f))
}
}
}
CardWithPadding {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
titledText(sent)
titledText(received)
titledView("From:") {
Row {
Box(modifier = Modifier.width(60.dp).height(60.dp))
Column {
titledText(user)
}
}
}
}
}
Card {
Column {
ItemButton("Reply", R.drawable.ic_reply)
Divider()
ItemButton("Resend", R.drawable.ic_reply)
Divider()
ItemButton("Delete", R.drawable.ic_delete_24, color = Color.Red)
}
}
}
}
}
if (messageRecord!!.expiresIn <= 0 || messageRecord!!.expireStarted <= 0) {
binding.expiresContainer.visibility = View.GONE
} else {
binding.expiresContainer.visibility = View.VISIBLE
val elapsed = SnodeAPI.nowWithOffset - messageRecord!!.expireStarted
val remaining = messageRecord!!.expiresIn - elapsed
@Composable
fun Divider() {
Divider(modifier = Modifier.padding(horizontal = 16.dp), thickness = 1.dp, color = Color(0xff414141))
}
val duration = ExpirationUtil.getExpirationDisplayValue(this, Math.max((remaining / 1000).toInt(), 1))
binding.expiresIn.text = duration
@Composable
fun ItemButton(text: String, @DrawableRes icon: Int, color: Color = Color.White) {
TextButton(
modifier = Modifier
.fillMaxWidth()
.height(60.dp),
onClick = {},
shape = RectangleShape,
) {
Box(modifier = Modifier.width(80.dp).fillMaxHeight()) {
Icon(
painter = painterResource(id = icon),
contentDescription = "",
tint = color,
modifier = Modifier.align(Alignment.Center)
)
}
Text(text, color = color, modifier = Modifier.fillMaxWidth())
}
}
@Composable
fun Card(content: @Composable () -> Unit) {
CardWithPadding(0.dp) { content() }
}
@Composable
fun CardWithPadding(padding: Dp = 24.dp, content: @Composable () -> Unit) {
Card(
shape = RoundedCornerShape(16.dp),
elevation = 0.dp,
modifier = Modifier
.wrapContentHeight()
.fillMaxWidth()
.padding(horizontal = 32.dp),
backgroundColor = Color(0xff1b1b1b),
contentColor = Color.White
) { Box(Modifier.padding(padding)) { content() } }
}
@Composable
fun titledText(titledText: TitledText, modifier: Modifier = Modifier) {
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) {
Title(titledText.title)
Text(titledText.value)
}
}
@Composable
fun titledView(title: String, modifier: Modifier = Modifier, content: @Composable () -> Unit) {
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) {
Title(title)
content()
}
}
@Composable
fun Title(text: String) {
Text(text, fontWeight = FontWeight.Bold)
}
}