kopia lustrzana https://github.com/ryukoposting/Signal-Android
Display message text in Media Preview.
rodzic
1b7e4e047c
commit
eaeeb08987
|
@ -2,23 +2,17 @@ package org.thoughtcrime.securesms.longmessage;
|
|||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage.ConversationMessageFactory;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage;
|
||||
import org.thoughtcrime.securesms.database.MessageTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.mms.TextSlide;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Optional;
|
||||
|
||||
class LongMessageRepository {
|
||||
|
@ -40,15 +34,9 @@ class LongMessageRepository {
|
|||
@WorkerThread
|
||||
private Optional<LongMessage> getMmsLongMessage(@NonNull Context context, @NonNull MessageTable mmsDatabase, long messageId) {
|
||||
Optional<MmsMessageRecord> record = getMmsMessage(mmsDatabase, messageId);
|
||||
|
||||
if (record.isPresent()) {
|
||||
TextSlide textSlide = record.get().getSlideDeck().getTextSlide();
|
||||
|
||||
if (textSlide != null && textSlide.getUri() != null) {
|
||||
return Optional.of(new LongMessage(ConversationMessageFactory.createWithUnresolvedData(context, record.get(), readFullBody(context, textSlide.getUri()))));
|
||||
} else {
|
||||
return Optional.of(new LongMessage(ConversationMessageFactory.createWithUnresolvedData(context, record.get())));
|
||||
}
|
||||
final ConversationMessage resolvedMessage = LongMessageResolveerKt.resolveBody(record.get(), context);
|
||||
return Optional.of(new LongMessage(resolvedMessage));
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
@ -61,14 +49,6 @@ class LongMessageRepository {
|
|||
}
|
||||
}
|
||||
|
||||
private @NonNull String readFullBody(@NonNull Context context, @NonNull Uri uri) {
|
||||
try (InputStream stream = PartAuthority.getAttachmentStream(context, uri)) {
|
||||
return StreamUtil.readFullyAsString(stream);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to read full text body.", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
interface Callback<T> {
|
||||
void onComplete(T result);
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package org.thoughtcrime.securesms.longmessage
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import org.signal.core.util.StreamUtil
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.conversation.ConversationMessage
|
||||
import org.thoughtcrime.securesms.database.model.MmsMessageRecord
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import java.io.IOException
|
||||
|
||||
const val TAG = "LongMessageResolver"
|
||||
|
||||
fun readFullBody(context: Context, uri: Uri): String {
|
||||
try {
|
||||
PartAuthority.getAttachmentStream(context, uri).use { stream -> return StreamUtil.readFullyAsString(stream) }
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Failed to read full text body.", e)
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
fun MmsMessageRecord.resolveBody(context: Context): ConversationMessage {
|
||||
val textSlide = slideDeck.textSlide
|
||||
val textSlideUri = textSlide?.uri
|
||||
return if (textSlide != null && textSlideUri != null) {
|
||||
ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, this, readFullBody(context, textSlideUri))
|
||||
} else {
|
||||
ConversationMessage.ConversationMessageFactory.createWithUnresolvedData(context, this)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@
|
|||
android:visibility="invisible"
|
||||
tools:visibility="visible">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
<org.thoughtcrime.securesms.mediapreview.caption.ExpandingCaptionView
|
||||
android:id="@+id/media_preview_caption"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -2010,6 +2010,8 @@
|
|||
<string name="MediaPreviewFragment_edit_media_error">Media Error</string>
|
||||
<!-- This is displayed as a toast notification when we encounter an error deleting a message, including potentially on other people\'s devices -->
|
||||
<string name="MediaPreviewFragment_media_delete_error">Error Deleting Message, Message May Still Exist</string>
|
||||
<!-- A suffix to be attached to truncated captions that the user may tap onto to view the entire text caption -->
|
||||
<string name="MediaPreviewFragment_see_more">%1$s… <b>Read More</b></string>
|
||||
|
||||
<!-- MessageNotifier -->
|
||||
<string name="MessageNotifier_d_new_messages_in_d_conversations">%1$d new messages in %2$d conversations</string>
|
||||
|
|
Ładowanie…
Reference in New Issue