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.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.requiredWidth
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
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.PassphraseRequiredActionBarActivity
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.Avatar
import org.thoughtcrime.securesms.ui.CarouselNextButton
@ -103,21 +103,15 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() {
override fun onCreate(savedInstanceState: Bundle?, ready: Boolean) {
super.onCreate(savedInstanceState, ready)
timestamp = intent.getLongExtra(MESSAGE_TIMESTAMP, -1L)
title = resources.getString(R.string.conversation_context__menu_message_details)
val messageRecord =
DatabaseComponent.get(this).mmsSmsDatabase().getMessageForTimestamp(timestamp) ?: run {
intent.getLongExtra(MESSAGE_TIMESTAMP, -1L).let(viewModel::setMessageTimestamp)
if (viewModel.details.value == null) {
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)
ComposeView(this)
.apply { setContent { MessageDetailsScreen() } }
.let(::setContentView)
@ -125,28 +119,27 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() {
@Composable
private fun MessageDetailsScreen() {
val details by viewModel.details.observeAsState(MessageDetails())
val threadDb = DatabaseComponent.get(this@MessageDetailActivity).threadDatabase()
val state by viewModel.details.observeAsState(MessageDetailsState())
AppTheme {
MessageDetails(
threadDb = threadDb,
messageDetails = details,
state = state,
onReply = { setResultAndFinish(ON_REPLY) },
onResend = details.error?.let { { setResultAndFinish(ON_RESEND) } },
onResend = state.error?.let { { setResultAndFinish(ON_RESEND) } },
onDelete = { setResultAndFinish(ON_DELETE) },
onClickImage = { slide ->
onClickImage = { i ->
val slide = state.attachments[i].slide
// only open to downloaded images
if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) {
// Restart download here (on IO thread)
(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(
this,
slide,
details.mmsRecord,
threadDb.getRecipientForThreadId(details.mmsRecord!!.threadId),
state.mmsRecord,
state.thread,
).let(::startActivity)
},
onAttachmentNeedsDownload = ::onAttachmentNeedsDownload,
@ -174,7 +167,7 @@ class MessageDetailActivity : PassphraseRequiredActionBarActivity() {
fun PreviewMessageDetails() {
AppTheme {
MessageDetails(
messageDetails = MessageDetails(
state = MessageDetailsState(
attachments = listOf(),
sent = TitledText("Sent:", "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")
@Composable
fun MessageDetails(
threadDb: ThreadDatabase? = null,
messageDetails: MessageDetails,
state: MessageDetailsState,
onReply: () -> Unit = {},
onResend: (() -> Unit)? = null,
onDelete: () -> Unit = {},
onClickImage: (Slide) -> Unit = {},
onClickImage: (Int) -> Unit = {},
onAttachmentNeedsDownload: (Long, Long) -> Unit = { _, _ -> }
) {
Column(
@ -202,14 +194,14 @@ fun MessageDetails(
.padding(vertical = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
messageDetails.record?.let { message ->
state.record?.let { message ->
AndroidView(
modifier = Modifier.padding(horizontal = 32.dp),
factory = {
ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(it)).mainContainerConstraint.apply {
bind(
message,
thread = threadDb?.getRecipientForThreadId(message.threadId)!!,
thread = state.thread!!,
onAttachmentNeedsDownload = onAttachmentNeedsDownload,
suppressThumbnails = true
)
@ -222,8 +214,9 @@ fun MessageDetails(
}
)
}
Carousel(messageDetails.attachments) { onClickImage(it) }
MetadataCell(messageDetails)
Carousel(state.imageAttachments) { onClickImage(it) }
state.nonImageAttachment?.fileDetails?.let { FileDetails(it) }
MetadataCell(state)
Buttons(
onReply,
onResend,
@ -234,9 +227,9 @@ fun MessageDetails(
@Composable
fun MetadataCell(
messageDetails: MessageDetails,
state: MessageDetailsState,
) {
messageDetails.apply {
state.apply {
if (sent != null || received != null || senderInfo != null) CellWithPaddingAndMargin {
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
sent?.let { TitledText(it) }
@ -289,25 +282,26 @@ fun Buttons(
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun Carousel(attachments: List<Attachment>, onClick: (Slide) -> Unit) {
val imageAttachments = attachments.filter { it.hasImage() }.takeIf { it.isNotEmpty() } ?: return
val pagerState = rememberPagerState { imageAttachments.size }
fun Carousel(attachments: List<Attachment>, onClick: (Int) -> Unit) {
if (attachments.isEmpty()) return
val pagerState = rememberPagerState { attachments.size }
Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
Row {
CarouselPrevButton(pagerState)
Box(modifier = Modifier.weight(1f)) {
CellCarousel(pagerState, imageAttachments, onClick)
CellCarousel(pagerState, attachments, onClick)
HorizontalPagerIndicator(pagerState)
ExpandButton(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(8.dp)
) { onClick(imageAttachments[pagerState.currentPage].slide) }
) { onClick(pagerState.currentPage) }
}
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
private fun CellCarousel(
pagerState: PagerState,
imageAttachments: List<Attachment>,
onClick: (Slide) -> Unit
attachments: List<Attachment>,
onClick: (Int) -> 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() ?: stringResource(id = R.string.image)
.clickable { onClick(i) },
model = attachments[i].uri,
contentDescription = attachments[i].fileName ?: stringResource(id = R.string.image)
)
}
}
@ -346,7 +339,7 @@ fun ExpandButton(modifier: Modifier = Modifier, onClick: () -> Unit) {
) {
Icon(
painter = painterResource(id = R.drawable.ic_expand),
contentDescription = "",
contentDescription = stringResource(id = R.string.expand),
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)
@Composable
fun FileDetails(fileDetails: List<TitledText>) {
@ -393,17 +380,18 @@ fun FileDetails(fileDetails: List<TitledText>) {
CellWithPaddingAndMargin {
FlowRow(verticalArrangement = Arrangement.spacedBy(16.dp)) {
fileDetails.forEach {
BoxWithConstraints {
TitledText(
it,
modifier = Modifier
.widthIn(min = 100.dp) // set minimum width
.width(IntrinsicSize.Max) // make the text as wide as necessary
.weight(1f) // space evenly
.widthIn(min = maxWidth.div(2))
.width(IntrinsicSize.Max)
)
}
}
}
}
}
@Composable
fun TitledErrorText(titledText: TitledText, modifier: Modifier = Modifier) {
@ -431,7 +419,7 @@ fun TitledText(
) {
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(4.dp)) {
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.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
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.utilities.Util
import org.session.libsession.utilities.recipients.Recipient
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.MmsMessageRecord
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 MessageDetails(
data class MessageDetailsState(
val attachments: List<Attachment> = emptyList(),
val record: MessageRecord? = null,
val mmsRecord: MmsMessageRecord? = null,
@ -26,44 +34,57 @@ data class MessageDetails(
val received: TitledText? = null,
val error: 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(
val slide: Slide,
val fileDetails: List<TitledText>
) {
val fileName: String? get() = slide.fileName.orNull()
val uri get() = slide.uri
fun hasImage() = slide is ImageSlide
}
@HiltViewModel
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() {
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 slides: List<Slide> = mmsRecord?.slideDeck?.thumbnailSlides?.toList() ?: emptyList()
_details.value = record?.run {
MessageDetails(
MessageDetailsState(
attachments = slides.map { Attachment(it, it.details) },
record = record,
mmsRecord = mmsRecord,
attachments = slides.map { Attachment(it, it.details) },
sent = dateSent.let(::Date).toString().let { TitledText("Sent:", it) },
received = dateReceived.let(::Date).toString().let { TitledText("Received:", it) },
error = error?.let { TitledText("Error:", it) },
senderInfo = individualRecipient.run {
name?.let { TitledText(it, address.serialize()) }
},
sender = individualRecipient
error = lokiMessageDatabase.getErrorMessage(id)?.let { TitledText("Error:", it) },
senderInfo = individualRecipient.run { name?.let { TitledText(it, address.serialize()) } },
sender = individualRecipient,
thread = threadDb.getRecipientForThreadId(threadId)!!,
)
}
}
private var _details = MutableLiveData(MessageDetails())
val details: LiveData<MessageDetails> = _details
private var _details = MutableLiveData(MessageDetailsState())
val details: LiveData<MessageDetailsState> = _details
private val Slide.details: List<TitledText>
get() = listOfNotNull(
@ -88,4 +109,5 @@ class MessageDetailsViewModel @Inject constructor(
TimeUnit.MILLISECONDS.toSeconds(it) % 60
)
}
}

View File

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

View File

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

View File

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