mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-25 11:20:47 +01:00
Display message text in Media Preview.
This commit is contained in:
committed by
Greyson Parrelli
parent
1b7e4e047c
commit
eaeeb08987
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.mediapreview
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.SpannableString
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.rxjava3.core.Completable
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
@@ -19,6 +20,8 @@ import org.thoughtcrime.securesms.database.MediaTable.Sorting
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase.Companion.media
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.longmessage.resolveBody
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.sms.MessageSender
|
||||
import org.thoughtcrime.securesms.util.AttachmentUtil
|
||||
@@ -38,7 +41,7 @@ class MediaPreviewRepository {
|
||||
* @param sorting the ordering of the results
|
||||
* @param limit the maximum quantity of the results
|
||||
*/
|
||||
fun getAttachments(startingAttachmentId: AttachmentId, threadId: Long, sorting: Sorting, limit: Int = 500): Flowable<Result> {
|
||||
fun getAttachments(context: Context, startingAttachmentId: AttachmentId, threadId: Long, sorting: Sorting, limit: Int = 500): Flowable<Result> {
|
||||
return Single.fromCallable {
|
||||
media.getGalleryMediaForThread(threadId, sorting).use { cursor ->
|
||||
val mediaRecords = mutableListOf<MediaTable.MediaRecord>()
|
||||
@@ -71,7 +74,12 @@ class MediaPreviewRepository {
|
||||
}
|
||||
}
|
||||
}
|
||||
Result(itemPosition, mediaRecords.toList())
|
||||
val messageIds = mediaRecords.mapNotNull { it.attachment?.mmsId }.toSet()
|
||||
val messages: Map<Long, SpannableString> = SignalDatabase.messages.getMessages(messageIds)
|
||||
.map { it as MmsMessageRecord }
|
||||
.associate { it.id to it.resolveBody(context).getDisplayBody(context) }
|
||||
|
||||
Result(itemPosition, mediaRecords.toList(), messages)
|
||||
}
|
||||
}.subscribeOn(Schedulers.io()).toFlowable()
|
||||
}
|
||||
@@ -110,5 +118,5 @@ class MediaPreviewRepository {
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
}
|
||||
|
||||
data class Result(val initialPosition: Int, val records: List<MediaTable.MediaRecord>)
|
||||
data class Result(val initialPosition: Int, val records: List<MediaTable.MediaRecord>, val messageBodies: Map<Long, SpannableString>)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.content.Intent
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.os.Bundle
|
||||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
@@ -48,6 +49,7 @@ import org.thoughtcrime.securesms.conversation.mutiselect.forward.MultiselectFor
|
||||
import org.thoughtcrime.securesms.database.MediaTable
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase
|
||||
import org.thoughtcrime.securesms.databinding.FragmentMediaPreviewV2Binding
|
||||
import org.thoughtcrime.securesms.mediapreview.caption.ExpandingCaptionView
|
||||
import org.thoughtcrime.securesms.mediapreview.mediarail.CenterDecoration
|
||||
import org.thoughtcrime.securesms.mediapreview.mediarail.MediaRailAdapter
|
||||
import org.thoughtcrime.securesms.mediapreview.mediarail.MediaRailAdapter.ImageLoadingListener
|
||||
@@ -127,7 +129,7 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
|
||||
}
|
||||
viewModel.initialize(args.showThread, args.allMediaInRail, args.leftIsRecent)
|
||||
val sorting = MediaTable.Sorting.deserialize(args.sorting.ordinal)
|
||||
viewModel.fetchAttachments(PartAuthority.requireAttachmentId(args.initialMediaUri), args.threadId, sorting)
|
||||
viewModel.fetchAttachments(requireContext(), PartAuthority.requireAttachmentId(args.initialMediaUri), args.threadId, sorting)
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
@@ -231,7 +233,7 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
|
||||
}
|
||||
}
|
||||
|
||||
bindTextViews(currentItem, currentState.showThread)
|
||||
bindTextViews(currentItem, currentState.showThread, currentState.messageBodies)
|
||||
bindMenuItems(currentItem)
|
||||
bindMediaPreviewPlaybackControls(currentItem, getMediaPreviewFragmentFromChildFragmentManager(currentPosition))
|
||||
|
||||
@@ -245,7 +247,7 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
|
||||
crossfadeViewIn(binding.mediaPreviewDetailsContainer)
|
||||
}
|
||||
|
||||
private fun bindTextViews(currentItem: MediaTable.MediaRecord, showThread: Boolean) {
|
||||
private fun bindTextViews(currentItem: MediaTable.MediaRecord, showThread: Boolean, messageBodies: Map<Long, SpannableString>) {
|
||||
binding.toolbar.title = getTitleText(currentItem, showThread)
|
||||
binding.toolbar.subtitle = getSubTitleText(currentItem)
|
||||
val messageId: Long? = currentItem.attachment?.mmsId
|
||||
@@ -262,8 +264,27 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
|
||||
}
|
||||
|
||||
val caption = currentItem.attachment?.caption
|
||||
binding.mediaPreviewCaption.text = caption
|
||||
binding.mediaPreviewCaption.visible = caption != null
|
||||
if (caption != null) {
|
||||
bindCaptionView(caption)
|
||||
} else {
|
||||
bindCaptionView(messageBodies[messageId])
|
||||
}
|
||||
}
|
||||
|
||||
private fun bindCaptionView(displayBody: CharSequence?) {
|
||||
val caption: ExpandingCaptionView = binding.mediaPreviewCaption
|
||||
if (displayBody.isNullOrEmpty()) {
|
||||
caption.visible = false
|
||||
} else {
|
||||
caption.expandedHeight = calculateExpandedHeight()
|
||||
caption.fullCaptionText = displayBody
|
||||
caption.visible = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateExpandedHeight(): Int {
|
||||
val height: Int = view?.height ?: return ViewUtil.dpToPx(requireContext(), EXPANDED_CAPTION_HEIGHT_FALLBACK_DP)
|
||||
return ((height - binding.toolbar.height - binding.mediaPreviewPlaybackControls.height) * EXPANDED_CAPTION_HEIGHT_PERCENT).roundToInt()
|
||||
}
|
||||
|
||||
private fun bindMenuItems(currentItem: MediaTable.MediaRecord) {
|
||||
@@ -602,6 +623,9 @@ class MediaPreviewV2Fragment : LoggingFragment(R.layout.fragment_media_preview_v
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EXPANDED_CAPTION_HEIGHT_FALLBACK_DP = 400
|
||||
private const val EXPANDED_CAPTION_HEIGHT_PERCENT: Float = 0.7F
|
||||
|
||||
private val TAG = Log.tag(MediaPreviewV2Fragment::class.java)
|
||||
|
||||
const val ARGS_KEY: String = "args"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.mediapreview
|
||||
|
||||
import android.text.SpannableString
|
||||
import org.thoughtcrime.securesms.database.MediaTable
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
|
||||
@@ -11,6 +12,7 @@ data class MediaPreviewV2State(
|
||||
val allMediaInAlbumRail: Boolean = false,
|
||||
val leftIsRecent: Boolean = false,
|
||||
val albums: Map<Long, List<Media>> = mapOf(),
|
||||
val messageBodies: Map<Long, SpannableString> = mapOf(),
|
||||
) {
|
||||
enum class LoadState { INIT, DATA_LOADED, MEDIA_READY }
|
||||
}
|
||||
|
||||
@@ -27,9 +27,9 @@ class MediaPreviewV2ViewModel : ViewModel() {
|
||||
val currentPosition: Int
|
||||
get() = store.state.position
|
||||
|
||||
fun fetchAttachments(startingAttachmentId: AttachmentId, threadId: Long, sorting: MediaTable.Sorting, forceRefresh: Boolean = false) {
|
||||
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(startingAttachmentId, threadId, sorting)) { result: MediaPreviewRepository.Result, oldState: MediaPreviewV2State ->
|
||||
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) {
|
||||
@@ -42,6 +42,7 @@ class MediaPreviewV2ViewModel : ViewModel() {
|
||||
oldState.copy(
|
||||
position = result.initialPosition,
|
||||
mediaRecords = result.records,
|
||||
messageBodies = result.messageBodies,
|
||||
albums = albums,
|
||||
loadState = MediaPreviewV2State.LoadState.DATA_LOADED,
|
||||
)
|
||||
@@ -49,6 +50,7 @@ class MediaPreviewV2ViewModel : ViewModel() {
|
||||
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,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package org.thoughtcrime.securesms.mediapreview.caption
|
||||
|
||||
import android.content.Context
|
||||
import android.text.method.ScrollingMovementMethod
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
|
||||
class ExpandingCaptionView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
) : EmojiTextView(context, attrs, defStyleAttr) {
|
||||
|
||||
var expandedHeight = 0
|
||||
|
||||
private var expanded: Boolean = false
|
||||
|
||||
var fullCaptionText: CharSequence = ""
|
||||
set(value) {
|
||||
field = value
|
||||
expanded = false
|
||||
updateExpansionState()
|
||||
}
|
||||
|
||||
init {
|
||||
setOnClickListener { toggleExpansion() }
|
||||
}
|
||||
|
||||
fun toggleExpansion() {
|
||||
expanded = !expanded
|
||||
updateExpansionState()
|
||||
}
|
||||
|
||||
private fun updateExpansionState() {
|
||||
if (expanded) {
|
||||
Log.d(TAG, "The view should be expanded now.")
|
||||
text = fullCaptionText
|
||||
movementMethod = ScrollingMovementMethod()
|
||||
scrollTo(0, 0)
|
||||
updateLayoutParams { height = expandedHeight }
|
||||
} else {
|
||||
Log.d(TAG, "The view should be collapsed now.")
|
||||
text = if (fullCaptionText.length <= CHAR_LIMIT_MESSAGE_PREVIEW) {
|
||||
fullCaptionText
|
||||
} else {
|
||||
context.getString(R.string.MediaPreviewFragment_see_more, fullCaptionText.substring(0, CHAR_LIMIT_MESSAGE_PREVIEW))
|
||||
}
|
||||
movementMethod = null
|
||||
updateLayoutParams { height = ViewGroup.LayoutParams.WRAP_CONTENT }
|
||||
}
|
||||
setOnClickListener { toggleExpansion() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(ExpandingCaptionView::class.java)
|
||||
const val CHAR_LIMIT_MESSAGE_PREVIEW = 280
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user