Cleanup ViewModel

This commit is contained in:
andrew 2023-07-10 13:01:46 +09:30
parent 821327569e
commit a1e8ad2c37
5 changed files with 101 additions and 95 deletions

View File

@ -10,13 +10,16 @@ import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.HorizontalPager
@ -61,9 +64,6 @@ import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAt
import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.MediaPreviewActivity
import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity
import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.ui.AppTheme import org.thoughtcrime.securesms.ui.AppTheme
import org.thoughtcrime.securesms.ui.Avatar import org.thoughtcrime.securesms.ui.Avatar
import org.thoughtcrime.securesms.ui.CarouselNextButton import org.thoughtcrime.securesms.ui.CarouselNextButton
@ -103,21 +103,15 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) { override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready) super.onCreate(savedInstanceState, ready)
timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L)
val messageRecord =
DatabaseComponent.get(this).mmsSmsDatabase().getMessageForTimestamp(timestamp) ?: run {
finish()
return
}
val error = DatabaseComponent.get(this).lokiMessageDatabase()
.getErrorMessage(messageRecord.getId())
viewModel.setMessageRecord(messageRecord, error)
title = resources.getString(R.string.conversation_context__menu_message_details) title = resources.getString(R.string.conversation_context__menu_message_details)
intent.getLongExtra(MESSAGE_TIMESTAMP, -1L).let(viewModel::setMessageTimestamp)
if (viewModel.details.value == null) {
finish()
return
}
ComposeView(this) ComposeView(this)
.apply { setContent { MessageDetailsScreen() } } .apply { setContent { MessageDetailsScreen() } }
.let(::setContentView) .let(::setContentView)
@ -125,28 +119,27 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() {
@Composable @Composable
private fun MessageDetailsScreen() { private fun MessageDetailsScreen() {
val details by viewModel.details.observeAsState(MessageDetails()) val state by viewModel.details.observeAsState(MessageDetailsState())
val threadDb = DatabaseComponent.get(this@MessageDetailActivity).threadDatabase()
AppTheme { AppTheme {
MessageDetails( MessageDetails(
threadDb = threadDb, state = state,
messageDetails = details,
onReply = { setResultAndFinish(ON_REPLY) }, onReply = { setResultAndFinish(ON_REPLY) },
onResend = details.error?.let { { setResultAndFinish(ON_RESEND) } }, onResend = state.error?.let { { setResultAndFinish(ON_RESEND) } },
onDelete = { setResultAndFinish(ON_DELETE) }, onDelete = { setResultAndFinish(ON_DELETE) },
onClickImage = { slide -> onClickImage = { i ->
val slide = state.attachments[i].slide
// only open to downloaded images // only open to downloaded images
if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) {
// Restart download here (on IO thread) // Restart download here (on IO thread)
(slide.asAttachment() as? DatabaseAttachment)?.let { attachment -> (slide.asAttachment() as? DatabaseAttachment)?.let { attachment ->
onAttachmentNeedsDownload(attachment.attachmentId.rowId, details.mmsRecord!!.getId()) onAttachmentNeedsDownload(attachment.attachmentId.rowId, state.mmsRecord!!.getId())
} }
} }
if (!slide.isInProgress) MediaPreviewActivity.getPreviewIntent( if (!slide.isInProgress) MediaPreviewActivity.getPreviewIntent(
this, this,
slide, slide,
details.mmsRecord, state.mmsRecord,
threadDb.getRecipientForThreadId(details.mmsRecord!!.threadId), state.thread,
).let(::startActivity) ).let(::startActivity)
}, },
onAttachmentNeedsDownload = ::onAttachmentNeedsDownload, onAttachmentNeedsDownload = ::onAttachmentNeedsDownload,
@ -174,7 +167,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() {
fun PreviewMessageDetails() { fun PreviewMessageDetails() {
AppTheme { AppTheme {
MessageDetails( MessageDetails(
messageDetails = MessageDetails( state = MessageDetailsState(
attachments = listOf(), attachments = listOf(),
sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"), sent = TitledText("Sent:", "6:12 AM Tue, 09/08/2022"),
received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"), received = TitledText("Received:", "6:12 AM Tue, 09/08/2022"),
@ -188,12 +181,11 @@ fun PreviewMessageDetails() {
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@Composable @Composable
fun MessageDetails( fun MessageDetails(
threadDb: ThreadDatabase? = null, state: MessageDetailsState,
messageDetails: MessageDetails,
onReply: () -> Unit = {}, onReply: () -> Unit = {},
onResend: (() -> Unit)? = null, onResend: (() -> Unit)? = null,
onDelete: () -> Unit = {}, onDelete: () -> Unit = {},
onClickImage: (Slide) -> Unit = {}, onClickImage: (Int) -> Unit = {},
onAttachmentNeedsDownload: (Long, Long) -> Unit = { _, _ -> } onAttachmentNeedsDownload: (Long, Long) -> Unit = { _, _ -> }
) { ) {
Column( Column(
@ -202,14 +194,14 @@ fun MessageDetails(
.padding(vertical = 16.dp), .padding(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp) verticalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
messageDetails.record?.let { message -> state.record?.let { message ->
AndroidView( AndroidView(
modifier = Modifier.padding(horizontal = 32.dp), modifier = Modifier.padding(horizontal = 32.dp),
factory = { factory = {
ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply { ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply {
bind( bind(
message, message,
thread = threadDb?.getRecipientForThreadId(message.threadId)!!, thread = state.thread!!,
onAttachmentNeedsDownload = onAttachmentNeedsDownload, onAttachmentNeedsDownload = onAttachmentNeedsDownload,
suppressThumbnails = true suppressThumbnails = true
) )
@ -222,8 +214,9 @@ fun MessageDetails(
} }
) )
} }
Carousel(messageDetails.attachments) { onClickImage(it) } Carousel(state.imageAttachments) { onClickImage(it) }
MetadataCell(messageDetails) state.nonImageAttachment?.fileDetails?.let { FileDetails(it) }
MetadataCell(state)
Buttons( Buttons(
onReply, onReply,
onResend, onResend,
@ -234,9 +227,9 @@ fun MessageDetails(
@Composable @Composable
fun MetadataCell( fun MetadataCell(
messageDetails: MessageDetails, state: MessageDetailsState,
) { ) {
messageDetails.apply { state.apply {
if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin { if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
sent?.let { TitledText(it) } sent?.let { TitledText(it) }
@ -289,25 +282,26 @@ fun Buttons(
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun Carousel(attachments: List<Attachment>, onClick: (Slide) -> Unit) { fun Carousel(attachments: List<Attachment>, onClick: (Int) -> Unit) {
val imageAttachments = attachments.filter { it.hasImage() }.takeIf { it.isNotEmpty() } ?: return if (attachments.isEmpty()) return
val pagerState = rememberPagerState { imageAttachments.size }
val pagerState = rememberPagerState { attachments.size }
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) { Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Row { Row {
CarouselPrevButton(pagerState) CarouselPrevButton(pagerState)
Box(modifier = Modifier.weight(1f)) { Box(modifier = Modifier.weight(1f)) {
CellCarousel(pagerState, imageAttachments, onClick) CellCarousel(pagerState, attachments, onClick)
HorizontalPagerIndicator(pagerState) HorizontalPagerIndicator(pagerState)
ExpandButton( ExpandButton(
modifier = Modifier modifier = Modifier
.align(Alignment.BottomEnd) .align(Alignment.BottomEnd)
.padding(8.dp) .padding(8.dp)
) { onClick(imageAttachments[pagerState.currentPage].slide) } ) { onClick(pagerState.currentPage) }
} }
CarouselNextButton(pagerState) CarouselNextButton(pagerState)
} }
FileDetails(attachments, pagerState) attachments.getOrNull(pagerState.currentPage)?.fileDetails?.let { FileDetails(it) }
} }
} }
@ -318,19 +312,18 @@ fun Carousel(attachments: List<Attachment>, onClick: (Slide) -> Unit) {
@Composable @Composable
private fun CellCarousel( private fun CellCarousel(
pagerState: PagerState, pagerState: PagerState,
imageAttachments: List<Attachment>, attachments: List<Attachment>,
onClick: (Slide) -> Unit onClick: (Int) -> Unit
) { ) {
CellNoMargin { CellNoMargin {
HorizontalPager(state = pagerState) { i -> HorizontalPager(state = pagerState) { i ->
val slide = imageAttachments[i].slide
GlideImage( GlideImage(
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
modifier = Modifier modifier = Modifier
.aspectRatio(1f) .aspectRatio(1f)
.clickable { onClick(slide) }, .clickable { onClick(i) },
model = slide.uri, model = attachments[i].uri,
contentDescription = slide.fileName.orNull() ?: stringResource(id = R.string.image) contentDescription = attachments[i].fileName ?: stringResource(id = R.string.image)
) )
} }
} }
@ -346,7 +339,7 @@ fun ExpandButton(modifier: Modifier = Modifier, onClick: () -> Unit) {
) { ) {
Icon( Icon(
painter = painterResource(id = R.drawable.ic_expand), painter = painterResource(id = R.drawable.ic_expand),
contentDescription = "", contentDescription = stringResource(id = R.string.expand),
modifier = Modifier.clickable { onClick() }, modifier = Modifier.clickable { onClick() },
) )
} }
@ -379,12 +372,6 @@ fun PreviewMessageDetails(
} }
} }
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun FileDetails(attachments: List<Attachment>, pagerState: PagerState) {
FileDetails(attachments[pagerState.currentPage].fileDetails)
}
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
fun FileDetails(fileDetails: List<TitledText>) { fun FileDetails(fileDetails: List<TitledText>) {
@ -393,13 +380,14 @@ fun FileDetails(fileDetails: List<TitledText>) {
CellWithPaddingAndMargin { CellWithPaddingAndMargin {
FlowRow(verticalArrangement = Arrangement.spacedBy(16.dp)) { FlowRow(verticalArrangement = Arrangement.spacedBy(16.dp)) {
fileDetails.forEach { fileDetails.forEach {
TitledText( BoxWithConstraints {
it, TitledText(
modifier = Modifier it,
.widthIn(min = 100.dp) // set minimum width modifier = Modifier
.width(IntrinsicSize.Max) // make the text as wide as necessary .widthIn(min = maxWidth.div(2))
.weight(1f) // space evenly .width(IntrinsicSize.Max)
) )
}
} }
} }
} }
@ -431,7 +419,7 @@ fun TitledText(
) { ) {
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) { Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) {
Title(titledText.title) Title(titledText.title)
Text(titledText.value, style = valueStyle) Text(titledText.value, style = valueStyle, modifier = Modifier.fillMaxWidth())
} }
} }

View File

@ -3,11 +3,19 @@ package org.thoughtcrime.securesms.conversation.v2
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.session.libsession.messaging.jobs.AttachmentDownloadJob
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.utilities.Util import org.session.libsession.utilities.Util
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.database.AttachmentDatabase import org.thoughtcrime.securesms.database.AttachmentDatabase
import org.thoughtcrime.securesms.database.LokiMessageDatabase
import org.thoughtcrime.securesms.database.MmsSmsDatabase
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
import org.thoughtcrime.securesms.mms.ImageSlide import org.thoughtcrime.securesms.mms.ImageSlide
@ -18,7 +26,7 @@ import javax.inject.Inject
data class TitledText(val title: String, val value: String) data class TitledText(val title: String, val value: String)
data class MessageDetails( data class MessageDetailsState(
val attachments: List<Attachment> = emptyList(), val attachments: List<Attachment> = emptyList(),
val record: MessageRecord? = null, val record: MessageRecord? = null,
val mmsRecord: MmsMessageRecord? = null, val mmsRecord: MmsMessageRecord? = null,
@ -26,44 +34,57 @@ data class MessageDetails(
val received: TitledText? = null, val received: TitledText? = null,
val error: TitledText? = null, val error: TitledText? = null,
val senderInfo: TitledText? = null, val senderInfo: TitledText? = null,
val sender: Recipient? = null val sender: Recipient? = null,
) val thread: Recipient? = null,
) {
val imageAttachments = attachments.filter { it.hasImage() }
val nonImageAttachment: Attachment? = attachments.firstOrNull { !it.hasImage() }
}
data class Attachment( data class Attachment(
val slide: Slide, val slide: Slide,
val fileDetails: List<TitledText> val fileDetails: List<TitledText>
) { ) {
val fileName: String? get() = slide.fileName.orNull()
val uri get() = slide.uri
fun hasImage() = slide is ImageSlide fun hasImage() = slide is ImageSlide
} }
@HiltViewModel @HiltViewModel
class MessageDetailsViewModel @Inject constructor( class MessageDetailsViewModel @Inject constructor(
private val attachmentDb: AttachmentDatabase private val attachmentDb: AttachmentDatabase,
private val lokiMessageDatabase: LokiMessageDatabase,
private val mmsSmsDatabase: MmsSmsDatabase,
private val threadDb: ThreadDatabase,
) : ViewModel() { ) : ViewModel() {
fun setMessageRecord(record: MessageRecord?, error: String?) { fun setMessageTimestamp(timestamp: Long) {
mmsSmsDatabase.getMessageForTimestamp(timestamp).let(::setMessageRecord)
}
fun setMessageRecord(record: MessageRecord?) {
val mmsRecord = record as? MmsMessageRecord val mmsRecord = record as? MmsMessageRecord
val slides: List<Slide> = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList() val slides: List<Slide> = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList()
_details.value = record?.run { _details.value = record?.run {
MessageDetails( MessageDetailsState(
attachments = slides.map { Attachment(it, it.details) },
record = record, record = record,
mmsRecord = mmsRecord, mmsRecord = mmsRecord,
attachments = slides.map { Attachment(it, it.details) },
sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) }, sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) },
received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) }, received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) },
error = error?.let { TitledText("Error:", it) }, error = lokiMessageDatabase.getErrorMessage(id)?.let { TitledText("Error:", it) },
senderInfo = individualRecipient.run { senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } },
name?.let { TitledText(it, address.serialize()) } sender = individualRecipient,
}, thread = threadDb.getRecipientForThreadId(threadId)!!,
sender = individualRecipient
) )
} }
} }
private var _details = MutableLiveData(MessageDetails()) private var _details = MutableLiveData(MessageDetailsState())
val details: LiveData<MessageDetails> = _details val details: LiveData<MessageDetailsState> = _details
private val Slide.details: List<TitledText> private val Slide.details: List<TitledText>
get() = listOfNotNull( get() = listOfNotNull(
@ -88,4 +109,5 @@ class MessageDetailsViewModel @Inject constructor(
TimeUnit.MILLISECONDS.toSeconds(it) % 60 TimeUnit.MILLISECONDS.toSeconds(it) % 60
) )
} }
} }

View File

@ -16,8 +16,6 @@ const val classicDark4 = 0xff767676
const val classicDark5 = 0xffA1A2A1 const val classicDark5 = 0xffA1A2A1
const val classicDark6 = 0xffFFFFFF const val classicDark6 = 0xffFFFFFF
const val classicLight0 = 0xff000000 const val classicLight0 = 0xff000000
const val classicLight1 = 0xff6D6D6D const val classicLight1 = 0xff6D6D6D
const val classicLight2 = 0xffA1A2A1 const val classicLight2 = 0xffA1A2A1

View File

@ -56,12 +56,6 @@ import org.session.libsession.utilities.recipients.Recipient
import org.thoughtcrime.securesms.components.ProfilePictureView import org.thoughtcrime.securesms.components.ProfilePictureView
import kotlin.math.roundToInt import kotlin.math.roundToInt
private val Colors.cellColors: Colors
@Composable
get() = MaterialTheme.colors.copy(
surface = LocalExtraColors.current.settingsBackground,
)
@Composable @Composable
fun ItemButton( fun ItemButton(
text: String, text: String,
@ -105,20 +99,23 @@ fun CellWithPaddingAndMargin(
margin: Dp = 32.dp, margin: Dp = 32.dp,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
MaterialTheme(colors = MaterialTheme.colors.cellColors) { Card(
Card( backgroundColor = MaterialTheme.colors.cellColor,
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
elevation = 0.dp, elevation = 0.dp,
modifier = Modifier modifier = Modifier
.wrapContentHeight() .wrapContentHeight()
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = margin), .padding(horizontal = margin),
) { ) {
Box(Modifier.padding(padding)) { content() } Box(Modifier.padding(padding)) { content() }
}
} }
} }
private val Colors.cellColor: Color
@Composable
get() = LocalExtraColors.current.settingsBackground
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
fun BoxScope.HorizontalPagerIndicator(pagerState: PagerState) { fun BoxScope.HorizontalPagerIndicator(pagerState: PagerState) {

View File

@ -12,6 +12,7 @@
<string name="image">Image</string> <string name="image">Image</string>
<string name="note_to_self">Note to Self</string> <string name="note_to_self">Note to Self</string>
<string name="version_s">Version %s</string> <string name="version_s">Version %s</string>
<string name="expand">Expand</string>
<!--Accessibility ID's--> <!--Accessibility ID's-->
<!-- Landing Page --> <!-- Landing Page -->
<string name="AccessibilityId_create_session_id">Create session ID</string> <string name="AccessibilityId_create_session_id">Create session ID</string>