Show media preview controls immediately.
This commit is contained in:
parent
39679ebfc3
commit
411a0198b4
@ -2954,10 +2954,16 @@ public final class ConversationItem extends RelativeLayout implements BindableCo
|
||||
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
|
||||
messageRecord.getThreadId(),
|
||||
messageRecord.getTimestamp(),
|
||||
messageRecord.getId(),
|
||||
messageRecord.getFromRecipient().getId(),
|
||||
conversationRecipient.getId(),
|
||||
messageRecord.isOutgoing(),
|
||||
mediaUri,
|
||||
slide.getUri(),
|
||||
slide.getContentType(),
|
||||
slide.asAttachment().size,
|
||||
slide.getCaption().orElse(null),
|
||||
conversationMessage.getDisplayBody(getContext()),
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
|
||||
@ -338,10 +338,16 @@ public final class MediaOverviewPageFragment extends LoggingFragment
|
||||
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
|
||||
threadId,
|
||||
mediaRecord.getDate(),
|
||||
mediaRecord.getMessageId(),
|
||||
mediaRecord.getRecipientId(),
|
||||
mediaRecord.getThreadRecipientId(),
|
||||
mediaRecord.isOutgoing(),
|
||||
Objects.requireNonNull(mediaRecord.getAttachment().getDisplayUri()),
|
||||
mediaRecord.getAttachment().getUri(),
|
||||
mediaRecord.getContentType(),
|
||||
mediaRecord.getAttachment().size,
|
||||
mediaRecord.getAttachment().caption,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
threadId == MediaTable.ALL_THREADS,
|
||||
|
||||
@ -6,11 +6,14 @@ import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import kotlinx.parcelize.WriteWith
|
||||
import org.signal.core.util.dp
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment
|
||||
import org.thoughtcrime.securesms.database.MediaTable
|
||||
import org.thoughtcrime.securesms.database.MediaTable.MediaRecord
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.util.parcelers.NullableCharSequenceParceler
|
||||
|
||||
object MediaIntentFactory {
|
||||
private const val ARGS_KEY = "args"
|
||||
@ -32,10 +35,16 @@ object MediaIntentFactory {
|
||||
data class MediaPreviewArgs(
|
||||
val threadId: Long,
|
||||
val date: Long,
|
||||
val messageId: Long,
|
||||
val fromRecipientId: RecipientId,
|
||||
val threadRecipientId: RecipientId,
|
||||
val outgoing: Boolean,
|
||||
val initialMediaUri: Uri,
|
||||
val initialMediaDataUri: Uri?,
|
||||
val initialMediaType: String?,
|
||||
val initialMediaSize: Long,
|
||||
val initialCaption: String? = null,
|
||||
val initialMessageBody: @WriteWith<NullableCharSequenceParceler> CharSequence? = null,
|
||||
val leftIsRecent: Boolean = false,
|
||||
val hideAllMedia: Boolean = false,
|
||||
val showThread: Boolean = false,
|
||||
@ -70,10 +79,16 @@ object MediaIntentFactory {
|
||||
MediaPreviewArgs(
|
||||
threadId = mediaRecord.threadId,
|
||||
date = mediaRecord.date,
|
||||
messageId = mediaRecord.messageId,
|
||||
fromRecipientId = mediaRecord.recipientId,
|
||||
threadRecipientId = mediaRecord.threadRecipientId,
|
||||
outgoing = mediaRecord.isOutgoing,
|
||||
initialMediaUri = attachment.displayUri!!,
|
||||
initialMediaDataUri = attachment.uri,
|
||||
initialMediaType = attachment.contentType,
|
||||
initialMediaSize = attachment.size,
|
||||
initialCaption = attachment.caption,
|
||||
initialMessageBody = null,
|
||||
leftIsRecent = leftIsRecent,
|
||||
allMediaInRail = allMediaInRail,
|
||||
sorting = MediaTable.Sorting.Newest,
|
||||
|
||||
@ -85,16 +85,19 @@ class MediaPreviewRepository {
|
||||
}
|
||||
}
|
||||
|
||||
val messageIds = mediaRecords.mapNotNull { it.attachment?.mmsId }.toSet()
|
||||
val messages: Map<Long, SpannableString> = SignalDatabase.messages.getMessages(messageIds).toList().withAttachments()
|
||||
.map { it as MmsMessageRecord }
|
||||
.associate { it.id to it.resolveBody(context).getDisplayBody(context) }
|
||||
|
||||
Result(if (mediaRecords.isNotEmpty()) itemPosition.coerceIn(mediaRecords.indices) else itemPosition, mediaRecords, messages)
|
||||
Result(if (mediaRecords.isNotEmpty()) itemPosition.coerceIn(mediaRecords.indices) else itemPosition, mediaRecords)
|
||||
}
|
||||
}.subscribeOn(Schedulers.io()).toFlowable()
|
||||
}
|
||||
|
||||
fun resolveMessageBodies(context: Context, messageIds: Set<Long>): Single<Map<Long, SpannableString>> {
|
||||
return Single.fromCallable {
|
||||
SignalDatabase.messages.getMessages(messageIds).toList().withAttachments()
|
||||
.filterIsInstance<MmsMessageRecord>()
|
||||
.associate { it.id to it.resolveBody(context).getDisplayBody(context) }
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
fun localDelete(attachment: DatabaseAttachment): Completable {
|
||||
return Completable.fromRunnable {
|
||||
val deletedMessageRecord = AttachmentUtil.deleteAttachment(attachment)
|
||||
@ -132,5 +135,5 @@ class MediaPreviewRepository {
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
data class Result(val initialPosition: Int, val records: List<MediaTable.MediaRecord>, val messageBodies: Map<Long, SpannableString>)
|
||||
data class Result(val initialPosition: Int, val records: List<MediaTable.MediaRecord>)
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.text.Annotation
|
||||
@ -71,6 +72,7 @@ import org.thoughtcrime.securesms.mediapreview.mediarail.MediaRailAdapter.ImageL
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionActivity
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.sharing.v2.ShareActivity
|
||||
import org.thoughtcrime.securesms.util.DateUtils
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper
|
||||
@ -96,6 +98,7 @@ class MediaPreviewV2Fragment :
|
||||
requireActivity()
|
||||
})
|
||||
private val debouncer = Debouncer(2, TimeUnit.SECONDS)
|
||||
private val args: MediaIntentFactory.MediaPreviewArgs by lazy { MediaIntentFactory.requireArguments(requireArguments()) }
|
||||
|
||||
private lateinit var pagerAdapter: MediaPreviewV2Adapter
|
||||
private lateinit var albumRailAdapter: MediaRailAdapter
|
||||
@ -118,9 +121,11 @@ class MediaPreviewV2Fragment :
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val args = MediaIntentFactory.requireArguments(requireArguments())
|
||||
initializeViewModel(args)
|
||||
initializeToolbar(binding.toolbar)
|
||||
bindToolbar(args.fromRecipientId, args.threadRecipientId, args.outgoing, args.showThread, args.date, args.messageId)
|
||||
bindInitialPlaybackControls(args)
|
||||
bindInitialCaption(args)
|
||||
initializeViewPager()
|
||||
initializeAlbumRail()
|
||||
initializeFullScreenUi()
|
||||
@ -244,6 +249,10 @@ class MediaPreviewV2Fragment :
|
||||
if (binding.mediaPager.currentItem != currentPosition) {
|
||||
binding.mediaPager.setCurrentItem(currentPosition, false)
|
||||
}
|
||||
|
||||
val currentItem: MediaTable.MediaRecord = currentState.mediaRecords[currentPosition]
|
||||
bindTextViews(currentItem, currentState.showThread, currentState.messageBodies)
|
||||
bindMenuItems(currentItem)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -282,13 +291,46 @@ class MediaPreviewV2Fragment :
|
||||
}
|
||||
|
||||
private fun bindTextViews(currentItem: MediaTable.MediaRecord, showThread: Boolean, messageBodies: Map<Long, SpannableString>) {
|
||||
val title = getTitleText(currentItem, showThread)
|
||||
val (subtitle, subtitleContentDesc) = getSubTitleText(currentItem)
|
||||
bindToolbar(currentItem.recipientId, currentItem.threadRecipientId, currentItem.isOutgoing, showThread, currentItem.date, currentItem.attachment?.mmsId)
|
||||
|
||||
val caption = currentItem.attachment?.caption
|
||||
val messageId = currentItem.attachment?.mmsId
|
||||
if (caption != null) {
|
||||
bindCaptionView(SpannableString(caption))
|
||||
} else {
|
||||
bindCaptionView(messageBodies[messageId] ?: initialBodyForMessage(messageId))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The body passed in via arguments for the initially-opened message. Used as a fallback until the background
|
||||
* body resolution populates [MediaPreviewV2State.messageBodies], so the caption never blanks out after the
|
||||
* instant render.
|
||||
*/
|
||||
private fun initialBodyForMessage(messageId: Long?): SpannableString? {
|
||||
return if (messageId != null && messageId == args.messageId) {
|
||||
args.initialMessageBody?.let { SpannableString(it) }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindInitialCaption(args: MediaIntentFactory.MediaPreviewArgs) {
|
||||
val caption = args.initialCaption
|
||||
if (caption != null) {
|
||||
bindCaptionView(SpannableString(caption))
|
||||
} else {
|
||||
bindCaptionView(args.initialMessageBody?.let { SpannableString(it) })
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindToolbar(fromRecipientId: RecipientId, threadRecipientId: RecipientId, isOutgoing: Boolean, showThread: Boolean, date: Long, messageId: Long?) {
|
||||
val title = getTitleText(fromRecipientId, threadRecipientId, isOutgoing, showThread)
|
||||
val (subtitle, subtitleContentDesc) = getSubTitleText(date)
|
||||
binding.toolbar.title = title
|
||||
binding.toolbar.subtitle = subtitle
|
||||
binding.toolbar.contentDescription = "$title $subtitleContentDesc"
|
||||
val messageId: Long? = currentItem.attachment?.mmsId
|
||||
if (messageId != null) {
|
||||
if (messageId != null && messageId > 0) {
|
||||
binding.toolbar.setOnClickListener { v ->
|
||||
lifecycleDisposable += viewModel.jumpToFragment(v.context, messageId).subscribeBy(
|
||||
onSuccess = {
|
||||
@ -302,13 +344,6 @@ class MediaPreviewV2Fragment :
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val caption = currentItem.attachment?.caption
|
||||
if (caption != null) {
|
||||
bindCaptionView(SpannableString(caption))
|
||||
} else {
|
||||
bindCaptionView(messageBodies[messageId])
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindCaptionView(displayBody: SpannableString?) {
|
||||
@ -345,6 +380,20 @@ class MediaPreviewV2Fragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindInitialPlaybackControls(args: MediaIntentFactory.MediaPreviewArgs) {
|
||||
if (!isContentTypeSupported(args.initialMediaType)) {
|
||||
return
|
||||
}
|
||||
val mediaMode: MediaPreviewPlayerControlView.MediaMode = if (args.isVideoGif) {
|
||||
MediaPreviewPlayerControlView.MediaMode.IMAGE
|
||||
} else {
|
||||
MediaPreviewPlayerControlView.MediaMode.fromString(args.initialMediaType)
|
||||
}
|
||||
binding.mediaPreviewPlaybackControls.setMediaMode(mediaMode)
|
||||
bindShareAndForwardButtons(args.threadId, args.initialMediaDataUri, args.initialMediaType)
|
||||
crossfadeViewIn(binding.mediaPreviewDetailsContainer)
|
||||
}
|
||||
|
||||
private fun bindMediaPreviewPlaybackControls(currentItem: MediaTable.MediaRecord, currentFragment: MediaPreviewFragment?) {
|
||||
val mediaType: MediaPreviewPlayerControlView.MediaMode = if (currentItem.attachment?.videoGif == true) {
|
||||
MediaPreviewPlayerControlView.MediaMode.IMAGE
|
||||
@ -352,19 +401,31 @@ class MediaPreviewV2Fragment :
|
||||
MediaPreviewPlayerControlView.MediaMode.fromString(currentItem.contentType)
|
||||
}
|
||||
binding.mediaPreviewPlaybackControls.setMediaMode(mediaType)
|
||||
val videoMediaPreviewFragment: VideoMediaPreviewFragment? = currentFragment as? VideoMediaPreviewFragment
|
||||
binding.mediaPreviewPlaybackControls.setShareButtonListener {
|
||||
videoMediaPreviewFragment?.pause()
|
||||
share(currentItem)
|
||||
}
|
||||
binding.mediaPreviewPlaybackControls.setForwardButtonListener {
|
||||
videoMediaPreviewFragment?.pause()
|
||||
forward(currentItem)
|
||||
}
|
||||
bindShareAndForwardButtons(currentItem.threadId, currentItem.attachment?.uri, currentItem.contentType)
|
||||
currentFragment?.setBottomButtonControls(binding.mediaPreviewPlaybackControls)
|
||||
currentFragment?.autoPlayIfNeeded()
|
||||
}
|
||||
|
||||
private fun bindShareAndForwardButtons(threadId: Long, uri: Uri?, contentType: String?) {
|
||||
if (uri == null) {
|
||||
binding.mediaPreviewPlaybackControls.setShareButtonListener(null)
|
||||
binding.mediaPreviewPlaybackControls.setForwardButtonListener(null)
|
||||
return
|
||||
}
|
||||
binding.mediaPreviewPlaybackControls.setShareButtonListener {
|
||||
pauseCurrentMediaIfVideo()
|
||||
share(uri, contentType)
|
||||
}
|
||||
binding.mediaPreviewPlaybackControls.setForwardButtonListener {
|
||||
pauseCurrentMediaIfVideo()
|
||||
forward(threadId, uri, contentType)
|
||||
}
|
||||
}
|
||||
|
||||
private fun pauseCurrentMediaIfVideo() {
|
||||
(getMediaPreviewFragmentFromChildFragmentManager(binding.mediaPager.currentItem) as? VideoMediaPreviewFragment)?.pause()
|
||||
}
|
||||
|
||||
private fun tryBindMediaPreviewPlaybackControls(
|
||||
currentItem: MediaTable.MediaRecord,
|
||||
currentPosition: Int,
|
||||
@ -453,9 +514,9 @@ class MediaPreviewV2Fragment :
|
||||
binding.mediaPager.setCurrentItem(position, true)
|
||||
}
|
||||
|
||||
private fun getTitleText(mediaRecord: MediaTable.MediaRecord, showThread: Boolean): String {
|
||||
val recipient: Recipient = Recipient.live(mediaRecord.recipientId).get()
|
||||
val defaultFromString: String = if (mediaRecord.isOutgoing) {
|
||||
private fun getTitleText(fromRecipientId: RecipientId, threadRecipientId: RecipientId, isOutgoing: Boolean, showThread: Boolean): String {
|
||||
val recipient: Recipient = Recipient.live(fromRecipientId).get()
|
||||
val defaultFromString: String = if (isOutgoing) {
|
||||
getString(R.string.MediaPreviewActivity_you)
|
||||
} else {
|
||||
recipient.getDisplayName(requireContext())
|
||||
@ -464,8 +525,8 @@ class MediaPreviewV2Fragment :
|
||||
return defaultFromString
|
||||
}
|
||||
|
||||
val threadRecipient = Recipient.live(mediaRecord.threadRecipientId).get()
|
||||
return if (mediaRecord.isOutgoing) {
|
||||
val threadRecipient = Recipient.live(threadRecipientId).get()
|
||||
return if (isOutgoing) {
|
||||
if (threadRecipient.isSelf) {
|
||||
getString(R.string.note_to_self)
|
||||
} else {
|
||||
@ -480,9 +541,9 @@ class MediaPreviewV2Fragment :
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSubTitleText(mediaRecord: MediaTable.MediaRecord): Pair<CharSequence, CharSequence> {
|
||||
val (text, contentDesc) = if (mediaRecord.date > 0) {
|
||||
DateUtils.getExtendedRelativeTimeSpanString(requireContext(), Locale.getDefault(), mediaRecord.date)
|
||||
private fun getSubTitleText(date: Long): Pair<CharSequence, CharSequence> {
|
||||
val (text, contentDesc) = if (date > 0) {
|
||||
DateUtils.getExtendedRelativeTimeSpanString(requireContext(), Locale.getDefault(), date)
|
||||
} else {
|
||||
Pair(getString(R.string.MediaPreviewActivity_draft), getString(R.string.MediaPreviewActivity_draft))
|
||||
}
|
||||
@ -554,43 +615,35 @@ class MediaPreviewV2Fragment :
|
||||
activity?.finish()
|
||||
}
|
||||
|
||||
private fun forward(mediaItem: MediaTable.MediaRecord) {
|
||||
val attachment = mediaItem.attachment
|
||||
val uri = attachment?.uri
|
||||
if (attachment != null && uri != null) {
|
||||
MultiselectForwardFragmentArgs.create(
|
||||
context = requireContext(),
|
||||
threadId = mediaItem.threadId,
|
||||
mediaUri = uri,
|
||||
contentType = attachment.contentType
|
||||
) { args: MultiselectForwardFragmentArgs ->
|
||||
MultiselectForwardFragment.showBottomSheet(childFragmentManager, args)
|
||||
}
|
||||
private fun forward(threadId: Long, uri: Uri, contentType: String?) {
|
||||
MultiselectForwardFragmentArgs.create(
|
||||
context = requireContext(),
|
||||
threadId = threadId,
|
||||
mediaUri = uri,
|
||||
contentType = contentType
|
||||
) { args: MultiselectForwardFragmentArgs ->
|
||||
MultiselectForwardFragment.showBottomSheet(childFragmentManager, args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun share(mediaItem: MediaTable.MediaRecord) {
|
||||
val attachment = mediaItem.attachment
|
||||
val uri = attachment?.uri
|
||||
if (attachment != null && uri != null) {
|
||||
val publicUri = PartAuthority.getAttachmentPublicUri(uri)
|
||||
val mimeType = Intent.normalizeMimeType(attachment.contentType)
|
||||
val shareIntent = ShareCompat.IntentBuilder(requireActivity())
|
||||
.setStream(publicUri)
|
||||
.setType(mimeType)
|
||||
.createChooserIntent()
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
private fun share(uri: Uri, contentType: String?) {
|
||||
val publicUri = PartAuthority.getAttachmentPublicUri(uri)
|
||||
val mimeType = Intent.normalizeMimeType(contentType)
|
||||
val shareIntent = ShareCompat.IntentBuilder(requireActivity())
|
||||
.setStream(publicUri)
|
||||
.setType(mimeType)
|
||||
.createChooserIntent()
|
||||
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
||||
if (Build.VERSION.SDK_INT < 34) {
|
||||
shareIntent.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, arrayOf(ComponentName(requireContext(), ShareActivity::class.java)))
|
||||
}
|
||||
if (Build.VERSION.SDK_INT < 34) {
|
||||
shareIntent.putExtra(Intent.EXTRA_EXCLUDE_COMPONENTS, arrayOf(ComponentName(requireContext(), ShareActivity::class.java)))
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(shareIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.w(TAG, "No activity existed to share the media.", e)
|
||||
Toast.makeText(requireContext(), R.string.MediaPreviewActivity_cant_find_an_app_able_to_share_this_media, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
try {
|
||||
startActivity(shareIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.w(TAG, "No activity existed to share the media.", e)
|
||||
Toast.makeText(requireContext(), R.string.MediaPreviewActivity_cant_find_an_app_able_to_share_this_media, Toast.LENGTH_LONG).show()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -59,36 +59,47 @@ class MediaPreviewV2ViewModel : ViewModel() {
|
||||
|
||||
fun fetchAttachments(context: Context, startingAttachmentId: AttachmentId, threadId: Long, sorting: MediaTable.Sorting, forceRefresh: Boolean = false) {
|
||||
if (store.state.loadState == MediaPreviewV2State.LoadState.INIT || forceRefresh) {
|
||||
disposables += store.update(repository.getAttachments(context, startingAttachmentId, threadId, sorting)) { result: MediaPreviewRepository.Result, oldState: MediaPreviewV2State ->
|
||||
val albums = result.records.fold(mutableMapOf()) { acc: MutableMap<Long, MutableList<Media>>, mediaRecord: MediaTable.MediaRecord ->
|
||||
val attachment = mediaRecord.attachment
|
||||
if (attachment != null) {
|
||||
val convertedMedia = mediaRecord.toMedia() ?: return@fold acc
|
||||
acc.getOrPut(attachment.mmsId) { mutableListOf() }.add(convertedMedia)
|
||||
disposables += repository.getAttachments(context, startingAttachmentId, threadId, sorting).subscribe { result ->
|
||||
store.update { oldState ->
|
||||
val albums = result.records.fold(mutableMapOf()) { acc: MutableMap<Long, MutableList<Media>>, mediaRecord: MediaTable.MediaRecord ->
|
||||
val attachment = mediaRecord.attachment
|
||||
if (attachment != null) {
|
||||
val convertedMedia = mediaRecord.toMedia() ?: return@fold acc
|
||||
acc.getOrPut(attachment.mmsId) { mutableListOf() }.add(convertedMedia)
|
||||
}
|
||||
acc
|
||||
}
|
||||
if (oldState.leftIsRecent) {
|
||||
oldState.copy(
|
||||
position = result.initialPosition,
|
||||
mediaRecords = result.records,
|
||||
albums = albums,
|
||||
loadState = MediaPreviewV2State.LoadState.DATA_LOADED
|
||||
)
|
||||
} else {
|
||||
oldState.copy(
|
||||
position = result.records.size - result.initialPosition - 1,
|
||||
mediaRecords = result.records.reversed(),
|
||||
albums = albums.mapValues { it.value.reversed() },
|
||||
loadState = MediaPreviewV2State.LoadState.DATA_LOADED
|
||||
)
|
||||
}
|
||||
acc
|
||||
}
|
||||
if (oldState.leftIsRecent) {
|
||||
oldState.copy(
|
||||
position = result.initialPosition,
|
||||
mediaRecords = result.records,
|
||||
messageBodies = result.messageBodies,
|
||||
albums = albums,
|
||||
loadState = MediaPreviewV2State.LoadState.DATA_LOADED
|
||||
)
|
||||
} else {
|
||||
oldState.copy(
|
||||
position = result.records.size - result.initialPosition - 1,
|
||||
mediaRecords = result.records.reversed(),
|
||||
messageBodies = result.messageBodies,
|
||||
albums = albums.mapValues { it.value.reversed() },
|
||||
loadState = MediaPreviewV2State.LoadState.DATA_LOADED
|
||||
)
|
||||
}
|
||||
fetchMessageBodies(context, result.records)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchMessageBodies(context: Context, records: List<MediaTable.MediaRecord>) {
|
||||
val messageIds = records.mapNotNull { it.attachment?.mmsId }.toSet()
|
||||
if (messageIds.isEmpty()) {
|
||||
return
|
||||
}
|
||||
disposables += repository.resolveMessageBodies(context, messageIds).subscribe { bodies ->
|
||||
store.update { oldState -> oldState.copy(messageBodies = oldState.messageBodies + bodies) }
|
||||
}
|
||||
}
|
||||
|
||||
fun refetchAttachments(context: Context, startingAttachmentId: AttachmentId, threadId: Long, sorting: MediaTable.Sorting) {
|
||||
val state = store.state
|
||||
val currentAttachmentId = if (state.position in state.mediaRecords.indices) {
|
||||
|
||||
@ -382,10 +382,16 @@ public class AttachmentManager {
|
||||
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
|
||||
MediaIntentFactory.NOT_IN_A_THREAD,
|
||||
MediaIntentFactory.UNKNOWN_TIMESTAMP,
|
||||
MediaIntentFactory.NOT_IN_A_THREAD,
|
||||
RecipientId.UNKNOWN,
|
||||
RecipientId.UNKNOWN,
|
||||
true,
|
||||
slide.getUri(),
|
||||
slide.getUri(),
|
||||
slide.getContentType(),
|
||||
slide.asAttachment().size,
|
||||
slide.getCaption().orElse(null),
|
||||
null,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
|
||||
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright 2026 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.util.parcelers
|
||||
|
||||
import android.os.Parcel
|
||||
import android.text.TextUtils
|
||||
import kotlinx.parcelize.Parceler
|
||||
|
||||
/**
|
||||
* Parceler for a nullable [CharSequence], preserving any spans (e.g. mention and styling annotations).
|
||||
*/
|
||||
object NullableCharSequenceParceler : Parceler<CharSequence?> {
|
||||
override fun create(parcel: Parcel): CharSequence? {
|
||||
return TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel)
|
||||
}
|
||||
|
||||
override fun CharSequence?.write(parcel: Parcel, flags: Int) {
|
||||
TextUtils.writeToParcel(this, parcel, flags)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user