mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 04:58:45 +00:00
Add full send attachments.
This commit is contained in:
committed by
Cody Henthorne
parent
3879a8ffdb
commit
a966812bfc
@@ -134,6 +134,6 @@ object AvatarRenderer {
|
||||
}
|
||||
|
||||
private fun createMedia(uri: Uri, size: Long): Media {
|
||||
return Media(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), DIMENSIONS, DIMENSIONS, size, 0, false, false, Optional.empty(), Optional.empty(), Optional.empty())
|
||||
return Media(uri, MediaUtil.IMAGE_JPEG, System.currentTimeMillis(), DIMENSIONS, DIMENSIONS, size, 0, false, false, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +199,8 @@ data class MultiselectForwardFragmentArgs @JvmOverloads constructor(
|
||||
videoGif,
|
||||
Optional.empty(),
|
||||
Optional.ofNullable(caption),
|
||||
Optional.ofNullable(transformProperties)
|
||||
Optional.ofNullable(transformProperties),
|
||||
Optional.ofNullable(fileName)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,6 +249,7 @@ import org.thoughtcrime.securesms.messagedetails.MessageDetailsFragment
|
||||
import org.thoughtcrime.securesms.messagerequests.MessageRequestRepository
|
||||
import org.thoughtcrime.securesms.mms.AttachmentManager
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide
|
||||
import org.thoughtcrime.securesms.mms.DocumentSlide
|
||||
import org.thoughtcrime.securesms.mms.GifSlide
|
||||
import org.thoughtcrime.securesms.mms.ImageSlide
|
||||
import org.thoughtcrime.securesms.mms.MediaConstraints
|
||||
@@ -1206,7 +1207,7 @@ class ConversationFragment :
|
||||
|
||||
if (mediaType == SlideFactory.MediaType.VCARD) {
|
||||
conversationActivityResultContracts.launchContactShareEditor(uri, viewModel.recipientSnapshot!!.chatColors)
|
||||
} else if (mediaType == SlideFactory.MediaType.IMAGE || mediaType == SlideFactory.MediaType.GIF || mediaType == SlideFactory.MediaType.VIDEO) {
|
||||
} else {
|
||||
val mimeType = MediaUtil.getMimeType(requireContext(), uri) ?: mediaType.toFallbackMimeType()
|
||||
val media = Media(
|
||||
uri,
|
||||
@@ -1220,11 +1221,10 @@ class ConversationFragment :
|
||||
videoGif,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty()
|
||||
)
|
||||
conversationActivityResultContracts.launchMediaEditor(listOf(media), recipientId, composeText.textTrimmed)
|
||||
} else {
|
||||
attachmentManager.setMedia(Glide.with(this), uri, mediaType, MediaConstraints.getPushMediaConstraints(), width, height)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3614,6 +3614,7 @@ class ConversationFragment :
|
||||
MediaUtil.isVideoType(it.mimeType) -> VideoSlide(requireContext(), it.uri, it.size, it.isVideoGif, it.width, it.height, it.caption.orNull(), it.transformProperties.orNull())
|
||||
MediaUtil.isGif(it.mimeType) -> GifSlide(requireContext(), it.uri, it.size, it.width, it.height, it.isBorderless, it.caption.orNull())
|
||||
MediaUtil.isImageType(it.mimeType) -> ImageSlide(requireContext(), it.uri, it.mimeType, it.size, it.width, it.height, it.isBorderless, it.caption.orNull(), null, it.transformProperties.orNull())
|
||||
MediaUtil.isDocumentType(it.mimeType) -> { DocumentSlide(requireContext(), it.uri, it.mimeType, it.size, it.fileName.orNull()) }
|
||||
else -> {
|
||||
Log.w(TAG, "Asked to send an unexpected mimeType: '${it.mimeType}'. Skipping.")
|
||||
null
|
||||
|
||||
@@ -126,7 +126,7 @@ public class GiphyActivity extends PassphraseRequiredActivity implements Keyboar
|
||||
mimeType = mediaType.toFallbackMimeType();
|
||||
}
|
||||
|
||||
Media media = new Media(success.getBlobUri(), mimeType, 0, success.getWidth(), success.getHeight(), 0, 0, false, true, Optional.empty(), Optional.empty(), Optional.empty());
|
||||
Media media = new Media(success.getBlobUri(), mimeType, 0, success.getWidth(), success.getHeight(), 0, 0, false, true, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty());
|
||||
startActivityForResult(MediaSelectionActivity.editor(this, sendType, Collections.singletonList(media), recipientId, text), MEDIA_SENDER);
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ fun MediaTable.MediaRecord.toMedia(): Media? {
|
||||
attachment.videoGif,
|
||||
Optional.empty(),
|
||||
Optional.ofNullable(attachment.caption),
|
||||
Optional.empty(),
|
||||
Optional.empty()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -101,6 +101,7 @@ public class AvatarSelectionActivity extends AppCompatActivity implements Camera
|
||||
false,
|
||||
Optional.of(Media.ALL_MEDIA_BUCKET_ID),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty()));
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ public final class ImageEditorModelRenderMediaTransform implements MediaTransfor
|
||||
.withMimeType(MediaUtil.IMAGE_JPEG)
|
||||
.createForSingleSessionOnDisk(context);
|
||||
|
||||
return new Media(uri, MediaUtil.IMAGE_JPEG, media.getDate(), bitmap.getWidth(), bitmap.getHeight(), outputStream.size(), 0, false, false, media.getBucketId(), media.getCaption(), Optional.empty());
|
||||
return new Media(uri, MediaUtil.IMAGE_JPEG, media.getDate(), bitmap.getWidth(), bitmap.getHeight(), outputStream.size(), 0, false, false, media.getBucketId(), media.getCaption(), Optional.empty(), Optional.empty());
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to render image. Using base image.");
|
||||
return media;
|
||||
|
||||
@@ -31,9 +31,10 @@ public class Media implements Parcelable {
|
||||
private final boolean borderless;
|
||||
private final boolean videoGif;
|
||||
|
||||
private Optional<String> bucketId;
|
||||
private Optional<String> bucketId;
|
||||
private Optional<String> caption;
|
||||
private Optional<AttachmentTable.TransformProperties> transformProperties;
|
||||
private Optional<String> fileName;
|
||||
|
||||
public Media(@NonNull Uri uri,
|
||||
@NonNull String mimeType,
|
||||
@@ -46,7 +47,8 @@ public class Media implements Parcelable {
|
||||
boolean videoGif,
|
||||
Optional<String> bucketId,
|
||||
Optional<String> caption,
|
||||
Optional<AttachmentTable.TransformProperties> transformProperties)
|
||||
Optional<AttachmentTable.TransformProperties> transformProperties,
|
||||
Optional<String> fileName)
|
||||
{
|
||||
this.uri = uri;
|
||||
this.mimeType = mimeType;
|
||||
@@ -60,6 +62,7 @@ public class Media implements Parcelable {
|
||||
this.bucketId = bucketId;
|
||||
this.caption = caption;
|
||||
this.transformProperties = transformProperties;
|
||||
this.fileName = fileName;
|
||||
}
|
||||
|
||||
protected Media(Parcel in) {
|
||||
@@ -80,6 +83,7 @@ public class Media implements Parcelable {
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
fileName = Optional.ofNullable(in.readString());
|
||||
}
|
||||
|
||||
public Uri getUri() {
|
||||
@@ -130,6 +134,14 @@ public class Media implements Parcelable {
|
||||
this.caption = Optional.ofNullable(caption);
|
||||
}
|
||||
|
||||
public Optional<String> getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public void setFileName(String name) {
|
||||
this.fileName = Optional.ofNullable(name);
|
||||
}
|
||||
|
||||
public Optional<AttachmentTable.TransformProperties> getTransformProperties() {
|
||||
return transformProperties;
|
||||
}
|
||||
@@ -153,6 +165,7 @@ public class Media implements Parcelable {
|
||||
dest.writeString(bucketId.orElse(null));
|
||||
dest.writeString(caption.orElse(null));
|
||||
dest.writeString(transformProperties.map(JsonUtil::toJson).orElse(null));
|
||||
dest.writeString(fileName.orElse(null));
|
||||
}
|
||||
|
||||
public static final Creator<Media> CREATOR = new Creator<Media>() {
|
||||
@@ -194,7 +207,8 @@ public class Media implements Parcelable {
|
||||
media.isVideoGif(),
|
||||
media.getBucketId(),
|
||||
media.getCaption(),
|
||||
media.getTransformProperties());
|
||||
media.getTransformProperties(),
|
||||
media.getFileName());
|
||||
}
|
||||
|
||||
public static @NonNull Media stripTransform(@NonNull Media media) {
|
||||
@@ -211,6 +225,7 @@ public class Media implements Parcelable {
|
||||
media.isVideoGif(),
|
||||
media.getBucketId(),
|
||||
media.getCaption(),
|
||||
Optional.empty());
|
||||
Optional.empty(),
|
||||
media.getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,7 +275,7 @@ public class MediaRepository {
|
||||
long size = cursor.getLong(cursor.getColumnIndexOrThrow(Images.Media.SIZE));
|
||||
long duration = !isImage ? cursor.getInt(cursor.getColumnIndexOrThrow(Video.Media.DURATION)) : 0;
|
||||
|
||||
media.add(fixMimeType(context, new Media(uri, mimetype, date, width, height, size, duration, false, false, Optional.of(bucketId), Optional.empty(), Optional.empty())));
|
||||
media.add(fixMimeType(context, new Media(uri, mimetype, date, width, height, size, duration, false, false, Optional.of(bucketId), Optional.empty(), Optional.empty(), Optional.empty())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,7 +366,7 @@ public class MediaRepository {
|
||||
height = dimens.second;
|
||||
}
|
||||
|
||||
return new Media(media.getUri(), media.getMimeType(), media.getDate(), width, height, size, 0, media.isBorderless(), media.isVideoGif(), media.getBucketId(), media.getCaption(), Optional.empty());
|
||||
return new Media(media.getUri(), media.getMimeType(), media.getDate(), width, height, size, 0, media.isBorderless(), media.isVideoGif(), media.getBucketId(), media.getCaption(), Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
private Media getContentResolverPopulatedMedia(@NonNull Context context, @NonNull Media media) throws IOException {
|
||||
@@ -392,7 +392,7 @@ public class MediaRepository {
|
||||
height = dimens.second;
|
||||
}
|
||||
|
||||
return new Media(media.getUri(), media.getMimeType(), media.getDate(), width, height, size, 0, media.isBorderless(), media.isVideoGif(), media.getBucketId(), media.getCaption(), Optional.empty());
|
||||
return new Media(media.getUri(), media.getMimeType(), media.getDate(), width, height, size, 0, media.isBorderless(), media.isVideoGif(), media.getBucketId(), media.getCaption(), Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
package org.thoughtcrime.securesms.mediasend
|
||||
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.provider.OpenableColumns
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import java.io.IOException
|
||||
import java.util.Optional
|
||||
|
||||
/**
|
||||
* Fragment to show full screen document attachments
|
||||
*/
|
||||
class MediaSendDocumentFragment : Fragment(R.layout.mediasend_document_fragment), MediaSendPageFragment {
|
||||
|
||||
companion object {
|
||||
private val TAG = Log.tag(MediaSendDocumentFragment::class.java)
|
||||
|
||||
private const val KEY_MEDIA = "media"
|
||||
|
||||
fun newInstance(media: Media): MediaSendDocumentFragment {
|
||||
val args = Bundle()
|
||||
args.putParcelable(KEY_MEDIA, media)
|
||||
|
||||
val fragment = MediaSendDocumentFragment()
|
||||
fragment.arguments = args
|
||||
fragment.uri = media.uri
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var uri: Uri
|
||||
private lateinit var media: Media
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val name: TextView = view.findViewById(R.id.name)
|
||||
val size: TextView = view.findViewById(R.id.size)
|
||||
val extension: TextView = view.findViewById(R.id.extension)
|
||||
|
||||
this.media = requireNotNull(requireArguments().getParcelableCompat(KEY_MEDIA, Media::class.java))
|
||||
|
||||
val fileInfo: Pair<String?, Long>? = getFileInfo()
|
||||
if (fileInfo != null) {
|
||||
media.setFileName(fileInfo.first)
|
||||
name.text = fileInfo.first ?: getString(R.string.DocumentView_unnamed_file)
|
||||
size.text = Util.getPrettyFileSize(fileInfo.second)
|
||||
|
||||
val extensionText: String = MediaUtil.getFileType(requireContext(), Optional.ofNullable(fileInfo.first), media.uri).orElse("")
|
||||
if (extensionText.length <= 3) {
|
||||
extension.text = extensionText
|
||||
extension.setTextAppearance(requireContext(), R.style.Signal_Text_BodySmall)
|
||||
} else if (extensionText.length == 4) {
|
||||
extension.text = extensionText
|
||||
extension.setTextAppearance(requireContext(), R.style.Signal_Text_Caption)
|
||||
}
|
||||
} else {
|
||||
Toast.makeText(requireContext(), R.string.ConversationActivity_sorry_there_was_an_error_setting_your_attachment, Toast.LENGTH_SHORT).show()
|
||||
requireActivity().finishAfterTransition()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getUri(): Uri {
|
||||
return uri
|
||||
}
|
||||
|
||||
override fun setUri(uri: Uri) {
|
||||
this.uri = uri
|
||||
}
|
||||
|
||||
override fun saveState(): Any = Unit
|
||||
|
||||
override fun restoreState(state: Any) = Unit
|
||||
|
||||
override fun notifyHidden() = Unit
|
||||
|
||||
private fun getFileInfo(): Pair<String?, Long>? {
|
||||
val fileInfo: Pair<String?, Long>
|
||||
try {
|
||||
if (PartAuthority.isLocalUri(uri)) {
|
||||
fileInfo = getManuallyCalculatedFileInfo(uri)
|
||||
} else {
|
||||
val result = getContentResolverFileInfo(uri)
|
||||
fileInfo = if ((result == null)) getManuallyCalculatedFileInfo(uri) else result
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, e)
|
||||
return null
|
||||
}
|
||||
|
||||
return fileInfo
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun getManuallyCalculatedFileInfo(uri: Uri): Pair<String?, Long> {
|
||||
var fileName: String? = null
|
||||
var fileSize: Long? = null
|
||||
|
||||
if (PartAuthority.isLocalUri(uri)) {
|
||||
fileSize = PartAuthority.getAttachmentSize(requireContext(), uri)
|
||||
fileName = PartAuthority.getAttachmentFileName(requireContext(), uri)
|
||||
}
|
||||
if (fileSize == null) {
|
||||
fileSize = MediaUtil.getMediaSize(context, uri)
|
||||
}
|
||||
|
||||
return Pair(fileName, fileSize)
|
||||
}
|
||||
|
||||
private fun getContentResolverFileInfo(uri: Uri): Pair<String, Long>? {
|
||||
var cursor: Cursor? = null
|
||||
|
||||
try {
|
||||
cursor = requireContext().contentResolver.query(uri, null, null, null, null)
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
val fileName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
|
||||
val fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE))
|
||||
media.setFileName(fileName)
|
||||
|
||||
return Pair(fileName, fileSize)
|
||||
}
|
||||
} finally {
|
||||
cursor?.close()
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,7 @@ public final class SentMediaQualityTransform implements MediaTransform {
|
||||
media.isVideoGif(),
|
||||
media.getBucketId(),
|
||||
media.getCaption(),
|
||||
Optional.of(AttachmentTable.TransformProperties.forSentMediaQuality(media.getTransformProperties(), sentMediaQuality)));
|
||||
Optional.of(AttachmentTable.TransformProperties.forSentMediaQuality(media.getTransformProperties(), sentMediaQuality)),
|
||||
media.getFileName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ class VideoTrimTransform(private val data: VideoTrimData) : MediaTransform {
|
||||
media.isVideoGif,
|
||||
media.bucketId,
|
||||
media.caption,
|
||||
Optional.of(TransformProperties(false, data.isDurationEdited, data.startTimeUs, data.endTimeUs, SentMediaQuality.STANDARD.code, false))
|
||||
Optional.of(TransformProperties(false, data.isDurationEdited, data.startTimeUs, data.endTimeUs, SentMediaQuality.STANDARD.code, false)),
|
||||
media.fileName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ object MediaBuilder {
|
||||
videoGif: Boolean = false,
|
||||
bucketId: Optional<String> = Optional.empty(),
|
||||
caption: Optional<String> = Optional.empty(),
|
||||
transformProperties: Optional<AttachmentTable.TransformProperties> = Optional.empty()
|
||||
) = Media(uri, mimeType, date, width, height, size, duration, borderless, videoGif, bucketId, caption, transformProperties)
|
||||
transformProperties: Optional<AttachmentTable.TransformProperties> = Optional.empty(),
|
||||
fileName: Optional<String> = Optional.empty()
|
||||
) = Media(uri, mimeType, date, width, height, size, duration, borderless, videoGif, bucketId, caption, transformProperties, fileName)
|
||||
}
|
||||
|
||||
@@ -151,6 +151,21 @@ class MediaSelectionRepository(context: Context) {
|
||||
scheduleMessages(sendType, contacts.map { it.recipientId }, trimmedBody, updatedMedia, trimmedMentions, trimmedBodyRanges, isViewOnce, scheduledTime)
|
||||
emitter.onComplete()
|
||||
}
|
||||
} else if (MediaUtil.isDocumentType(selectedMedia.first().mimeType)) {
|
||||
Log.i(TAG, "Document. Skipping pre-upload.")
|
||||
emitter.onSuccess(
|
||||
MediaSendActivityResult(
|
||||
recipientId = singleRecipient!!.id,
|
||||
nonUploadedMedia = updatedMedia,
|
||||
body = trimmedBody,
|
||||
messageSendType = sendType,
|
||||
isViewOnce = isViewOnce,
|
||||
mentions = trimmedMentions,
|
||||
bodyRanges = trimmedBodyRanges,
|
||||
storyType = StoryType.NONE,
|
||||
scheduledTime = scheduledTime
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val splitMessage = MessageUtil.getSplitMessage(context, trimmedBody, sendType.calculateCharacters(trimmedBody).maxPrimaryMessageSize)
|
||||
val splitBody = splitMessage.body
|
||||
|
||||
@@ -421,7 +421,7 @@ class MediaSelectionViewModel(
|
||||
}
|
||||
|
||||
val filteredPreUploadMedia = if (destination is MediaSelectionDestination.SingleRecipient || !Stories.isFeatureEnabled()) {
|
||||
media
|
||||
media.filter { !MediaUtil.isDocumentType(it.mimeType) }
|
||||
} else {
|
||||
media.filter { Stories.MediaTransform.canPreUploadMedia(it) }
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ object MediaValidator {
|
||||
|
||||
var error: FilterError? = null
|
||||
if (!isAllMediaValid) {
|
||||
error = if (media.all { MediaUtil.isImageOrVideoType(it.mimeType) }) {
|
||||
error = if (media.all { MediaUtil.isImageOrVideoType(it.mimeType) || MediaUtil.isDocumentType(it.mimeType) }) {
|
||||
FilterError.ItemTooLarge
|
||||
} else {
|
||||
FilterError.ItemInvalidType
|
||||
@@ -53,7 +53,7 @@ object MediaValidator {
|
||||
return media
|
||||
.filter { m -> isSupportedMediaType(m.mimeType) }
|
||||
.filter { m ->
|
||||
MediaUtil.isImageAndNotGif(m.mimeType) || isValidGif(context, m, mediaConstraints) || isValidVideo(context, m, mediaConstraints)
|
||||
MediaUtil.isImageAndNotGif(m.mimeType) || isValidGif(context, m, mediaConstraints) || isValidVideo(context, m, mediaConstraints) || isValidDocument(context, m, mediaConstraints)
|
||||
}
|
||||
.filter { m ->
|
||||
!isStory || Stories.MediaTransform.getSendRequirements(m) != Stories.MediaTransform.SendRequirements.CAN_NOT_SEND
|
||||
@@ -68,8 +68,12 @@ object MediaValidator {
|
||||
return MediaUtil.isVideoType(media.mimeType) && media.size < mediaConstraints.getUncompressedVideoMaxSize(context)
|
||||
}
|
||||
|
||||
private fun isValidDocument(context: Context, media: Media, mediaConstraints: MediaConstraints): Boolean {
|
||||
return MediaUtil.isDocumentType(media.mimeType) && media.size < mediaConstraints.getDocumentMaxSize(context)
|
||||
}
|
||||
|
||||
private fun isSupportedMediaType(mimeType: String): Boolean {
|
||||
return MediaUtil.isGif(mimeType) || MediaUtil.isImageType(mimeType) || MediaUtil.isVideoType(mimeType)
|
||||
return MediaUtil.isGif(mimeType) || MediaUtil.isImageType(mimeType) || MediaUtil.isVideoType(mimeType) || MediaUtil.isDocumentType(mimeType)
|
||||
}
|
||||
|
||||
data class FilterResult(val filteredMedia: List<Media>, val filterError: FilterError?, val bucketId: String?)
|
||||
|
||||
@@ -106,6 +106,7 @@ class MediaCaptureRepository(context: Context) {
|
||||
false,
|
||||
Optional.of(Media.ALL_MEDIA_BUCKET_ID),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty()
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
@@ -160,6 +161,7 @@ class MediaCaptureRepository(context: Context) {
|
||||
false,
|
||||
Optional.of(bucketId),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package org.thoughtcrime.securesms.mediasend.v2.documents
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import org.signal.core.util.getParcelableCompat
|
||||
import org.thoughtcrime.securesms.R
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mediasend.MediaSendDocumentFragment
|
||||
import org.thoughtcrime.securesms.mediasend.v2.HudCommand
|
||||
import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
|
||||
|
||||
private const val DOCUMENT_TAG = "media.send.document.fragment"
|
||||
|
||||
/**
|
||||
* Fragment which ensures we fire off ResumeEntryTransition when viewing a document.
|
||||
*/
|
||||
class MediaReviewDocumentPageFragment : Fragment(R.layout.fragment_container) {
|
||||
|
||||
private lateinit var mediaSendDocumentFragment: MediaSendDocumentFragment
|
||||
|
||||
private val sharedViewModel: MediaSelectionViewModel by viewModels(ownerProducer = { requireActivity() })
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
mediaSendDocumentFragment = ensureFragment()
|
||||
sharedViewModel.sendCommand(HudCommand.ResumeEntryTransition)
|
||||
}
|
||||
|
||||
private fun ensureFragment(): MediaSendDocumentFragment {
|
||||
val fragmentInManager: MediaSendDocumentFragment? = childFragmentManager.findFragmentByTag(DOCUMENT_TAG) as? MediaSendDocumentFragment
|
||||
|
||||
return if (fragmentInManager != null) {
|
||||
fragmentInManager
|
||||
} else {
|
||||
val mediaSendDocumentFragment = MediaSendDocumentFragment.newInstance(requireMedia())
|
||||
|
||||
childFragmentManager.beginTransaction()
|
||||
.replace(
|
||||
R.id.fragment_container,
|
||||
mediaSendDocumentFragment,
|
||||
DOCUMENT_TAG
|
||||
)
|
||||
.commitAllowingStateLoss()
|
||||
|
||||
mediaSendDocumentFragment
|
||||
}
|
||||
}
|
||||
|
||||
private fun requireMedia(): Media = requireNotNull(requireArguments().getParcelableCompat(ARG_MEDIA, Media::class.java))
|
||||
|
||||
companion object {
|
||||
private const val ARG_MEDIA = "arg.media"
|
||||
|
||||
fun newInstance(media: Media): Fragment {
|
||||
return MediaReviewDocumentPageFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable(ARG_MEDIA, media)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ import org.thoughtcrime.securesms.mediasend.v2.MediaSelectionViewModel
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import org.thoughtcrime.securesms.stories.Stories
|
||||
import org.thoughtcrime.securesms.util.MediaUtil
|
||||
import org.thoughtcrime.securesms.util.ViewUtil
|
||||
import org.thoughtcrime.securesms.util.views.Stub
|
||||
import org.thoughtcrime.securesms.util.visible
|
||||
@@ -133,7 +134,7 @@ class AddMessageDialogFragment : KeyboardEntryDialogFragment(R.layout.v2_media_a
|
||||
binding.content.addAMessageInput.text = null
|
||||
dismiss()
|
||||
}
|
||||
binding.content.viewOnceToggle.visible = state.selectedMedia.size == 1 && !state.isStory
|
||||
binding.content.viewOnceToggle.visible = state.selectedMedia.size == 1 && !state.isStory && !MediaUtil.isDocumentType(state.focusedMedia?.mimeType)
|
||||
}
|
||||
|
||||
initializeMentions()
|
||||
|
||||
@@ -655,7 +655,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
}
|
||||
|
||||
private fun computeViewOnceButtonAnimators(state: MediaSelectionState): List<Animator> {
|
||||
return if (state.isTouchEnabled && state.selectedMedia.size == 1 && !state.isStory) {
|
||||
return if (state.isTouchEnabled && state.selectedMedia.size == 1 && !state.isStory && !MediaUtil.isDocumentType(state.focusedMedia?.mimeType)) {
|
||||
listOf(MediaReviewAnimatorController.getFadeInAnimator(viewOnceButton))
|
||||
} else {
|
||||
listOf(MediaReviewAnimatorController.getFadeOutAnimator(viewOnceButton))
|
||||
@@ -672,7 +672,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
|
||||
private fun computeAddMediaButtonsAnimators(state: MediaSelectionState): List<Animator> {
|
||||
return when {
|
||||
!state.isTouchEnabled || state.viewOnceToggleState == MediaSelectionState.ViewOnceToggleState.ONCE -> {
|
||||
!state.isTouchEnabled || state.viewOnceToggleState == MediaSelectionState.ViewOnceToggleState.ONCE || MediaUtil.isDocumentType(state.focusedMedia?.mimeType) -> {
|
||||
listOf(
|
||||
MediaReviewAnimatorController.getFadeOutAnimator(addMediaButton),
|
||||
MediaReviewAnimatorController.getFadeOutAnimator(selectionRecycler)
|
||||
@@ -706,7 +706,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
}
|
||||
|
||||
private fun computeSaveButtonAnimators(state: MediaSelectionState): List<Animator> {
|
||||
return if (state.isTouchEnabled && !MediaUtil.isVideo(state.focusedMedia?.mimeType)) {
|
||||
return if (state.isTouchEnabled && !MediaUtil.isVideo(state.focusedMedia?.mimeType) && !MediaUtil.isDocumentType(state.focusedMedia?.mimeType)) {
|
||||
listOf(
|
||||
MediaReviewAnimatorController.getFadeInAnimator(saveButton)
|
||||
)
|
||||
@@ -718,7 +718,7 @@ class MediaReviewFragment : Fragment(R.layout.v2_media_review_fragment), Schedul
|
||||
}
|
||||
|
||||
private fun computeQualityButtonAnimators(state: MediaSelectionState): List<Animator> {
|
||||
return if (state.isTouchEnabled && !state.isStory) {
|
||||
return if (state.isTouchEnabled && !state.isStory && !MediaUtil.isDocumentType(state.focusedMedia?.mimeType)) {
|
||||
listOf(MediaReviewAnimatorController.getFadeInAnimator(qualityButton))
|
||||
} else {
|
||||
listOf(MediaReviewAnimatorController.getFadeOutAnimator(qualityButton))
|
||||
|
||||
@@ -5,6 +5,7 @@ import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import org.thoughtcrime.securesms.mediasend.Media
|
||||
import org.thoughtcrime.securesms.mediasend.v2.documents.MediaReviewDocumentPageFragment
|
||||
import org.thoughtcrime.securesms.mediasend.v2.gif.MediaReviewGifPageFragment
|
||||
import org.thoughtcrime.securesms.mediasend.v2.images.MediaReviewImagePageFragment
|
||||
import org.thoughtcrime.securesms.mediasend.v2.videos.MediaReviewVideoPageFragment
|
||||
@@ -46,6 +47,7 @@ class MediaReviewFragmentPagerAdapter(fragment: Fragment) : FragmentStateAdapter
|
||||
MediaUtil.isGif(mediaItem.mimeType) -> MediaReviewGifPageFragment.newInstance(mediaItem.uri)
|
||||
MediaUtil.isImageType(mediaItem.mimeType) -> MediaReviewImagePageFragment.newInstance(mediaItem.uri)
|
||||
MediaUtil.isVideoType(mediaItem.mimeType) -> MediaReviewVideoPageFragment.newInstance(mediaItem.uri, mediaItem.isVideoGif)
|
||||
MediaUtil.isDocumentType(mediaItem.mimeType) -> MediaReviewDocumentPageFragment.newInstance(mediaItem)
|
||||
else -> {
|
||||
throw UnsupportedOperationException("Can only render images and videos. Found mimetype: '" + mediaItem.mimeType + "'")
|
||||
}
|
||||
|
||||
@@ -17,25 +17,19 @@
|
||||
package org.thoughtcrime.securesms.mms;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.provider.ContactsContract;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
@@ -50,7 +44,6 @@ import org.signal.core.util.concurrent.SettableFuture;
|
||||
import org.signal.core.util.concurrent.SimpleTask;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.components.AudioView;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
|
||||
@@ -58,7 +51,6 @@ import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.components.location.SignalMapView;
|
||||
import org.thoughtcrime.securesms.components.location.SignalPlace;
|
||||
import org.thoughtcrime.securesms.conversation.MessageSendType;
|
||||
import org.thoughtcrime.securesms.database.AttachmentTable;
|
||||
import org.thoughtcrime.securesms.database.MediaTable;
|
||||
import org.thoughtcrime.securesms.database.SignalDatabase;
|
||||
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
|
||||
@@ -256,149 +248,6 @@ public class AttachmentManager {
|
||||
attachmentListener.onAttachmentChanged();
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public ListenableFuture<Boolean> setMedia(@NonNull final RequestManager requestManager,
|
||||
@NonNull final Uri uri,
|
||||
@NonNull final SlideFactory.MediaType mediaType,
|
||||
@NonNull final MediaConstraints constraints,
|
||||
final int width,
|
||||
final int height)
|
||||
{
|
||||
inflateStub();
|
||||
|
||||
final SettableFuture<Boolean> result = new SettableFuture<>();
|
||||
|
||||
new AsyncTask<Void, Void, Slide>() {
|
||||
private boolean areConstraintsSatisfied = false;
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
thumbnail.clear(requestManager);
|
||||
thumbnail.showProgressSpinner();
|
||||
attachmentViewStub.get().setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable Slide doInBackground(Void... params) {
|
||||
Slide slide;
|
||||
try {
|
||||
if (PartAuthority.isLocalUri(uri)) {
|
||||
slide = getManuallyCalculatedSlideInfo(uri, width, height);
|
||||
} else {
|
||||
Slide result = getContentResolverSlideInfo(uri, width, height);
|
||||
slide = (result == null) ? getManuallyCalculatedSlideInfo(uri, width, height) : result;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
|
||||
this.areConstraintsSatisfied = areConstraintsSatisfied(context, slide, constraints);
|
||||
return slide;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(@Nullable final Slide slide) {
|
||||
if (slide == null) {
|
||||
attachmentViewStub.get().setVisibility(View.GONE);
|
||||
Toast.makeText(context,
|
||||
R.string.ConversationActivity_sorry_there_was_an_error_setting_your_attachment,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
result.set(false);
|
||||
} else if (!areConstraintsSatisfied) {
|
||||
attachmentViewStub.get().setVisibility(View.GONE);
|
||||
Toast.makeText(context,
|
||||
R.string.ConversationActivity_attachment_exceeds_size_limits,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
result.set(false);
|
||||
} else {
|
||||
setSlide(slide);
|
||||
attachmentViewStub.get().setVisibility(View.VISIBLE);
|
||||
|
||||
if (slide.hasAudio()) {
|
||||
audioView.setAudio((AudioSlide) slide, null, false, false);
|
||||
removableMediaView.display(audioView, false);
|
||||
result.set(true);
|
||||
} else if (slide.hasDocument()) {
|
||||
documentView.setDocument((DocumentSlide) slide, false);
|
||||
removableMediaView.display(documentView, false);
|
||||
result.set(true);
|
||||
} else {
|
||||
Attachment attachment = slide.asAttachment();
|
||||
result.deferTo(thumbnail.setImageResource(requestManager, slide, false, true, attachment.width, attachment.height));
|
||||
removableMediaView.display(thumbnail, mediaType == SlideFactory.MediaType.IMAGE);
|
||||
}
|
||||
|
||||
attachmentListener.onAttachmentChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Slide getContentResolverSlideInfo(Uri uri, int width, int height) {
|
||||
Cursor cursor = null;
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
cursor = context.getContentResolver().query(uri, null, null, null, null);
|
||||
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
String fileName = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
|
||||
long fileSize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
|
||||
String mimeType = context.getContentResolver().getType(uri);
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
Pair<Integer, Integer> dimens = MediaUtil.getDimensions(context, mimeType, uri);
|
||||
width = dimens.first;
|
||||
height = dimens.second;
|
||||
}
|
||||
|
||||
Log.d(TAG, "remote slide with size " + fileSize + " took " + (System.currentTimeMillis() - start) + "ms");
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, null, fileSize, width, height, false, null);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private @NonNull Slide getManuallyCalculatedSlideInfo(Uri uri, int width, int height) throws IOException {
|
||||
long start = System.currentTimeMillis();
|
||||
Long mediaSize = null;
|
||||
String fileName = null;
|
||||
String mimeType = null;
|
||||
boolean gif = false;
|
||||
AttachmentTable.TransformProperties transformProperties = null;
|
||||
|
||||
if (PartAuthority.isLocalUri(uri)) {
|
||||
mediaSize = PartAuthority.getAttachmentSize(context, uri);
|
||||
fileName = PartAuthority.getAttachmentFileName(context, uri);
|
||||
mimeType = PartAuthority.getAttachmentContentType(context, uri);
|
||||
gif = PartAuthority.getAttachmentIsVideoGif(context, uri);
|
||||
transformProperties = PartAuthority.getAttachmentTransformProperties(uri);
|
||||
}
|
||||
|
||||
if (mediaSize == null) {
|
||||
mediaSize = MediaUtil.getMediaSize(context, uri);
|
||||
}
|
||||
|
||||
if (mimeType == null) {
|
||||
mimeType = MediaUtil.getMimeType(context, uri);
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
Pair<Integer, Integer> dimens = MediaUtil.getDimensions(context, mimeType, uri);
|
||||
width = dimens.first;
|
||||
height = dimens.second;
|
||||
}
|
||||
|
||||
Log.d(TAG, "local slide with size " + mediaSize + " took " + (System.currentTimeMillis() - start) + "ms");
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, null, mediaSize, width, height, gif, transformProperties);
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public boolean isAttachmentPresent() {
|
||||
return attachmentViewStub.resolved() && attachmentViewStub.get().getVisibility() == View.VISIBLE;
|
||||
}
|
||||
@@ -531,16 +380,6 @@ public class AttachmentManager {
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private boolean areConstraintsSatisfied(final @NonNull Context context,
|
||||
final @Nullable Slide slide,
|
||||
final @NonNull MediaConstraints constraints)
|
||||
{
|
||||
return slide == null ||
|
||||
constraints.isSatisfied(context, slide.asAttachment()) ||
|
||||
constraints.canResize(slide.asAttachment());
|
||||
}
|
||||
|
||||
private void previewImageDraft(final @NonNull Slide slide) {
|
||||
if (MediaPreviewV2Fragment.isContentTypeSupported(slide.getContentType()) && slide.getUri() != null) {
|
||||
MediaIntentFactory.MediaPreviewArgs args = new MediaIntentFactory.MediaPreviewArgs(
|
||||
|
||||
@@ -230,34 +230,7 @@ public abstract class Slide {
|
||||
}
|
||||
|
||||
public @NonNull Optional<String> getFileType(@NonNull Context context) {
|
||||
Optional<String> fileName = getFileName();
|
||||
|
||||
if (fileName.isPresent()) {
|
||||
String fileType = getFileType(fileName);
|
||||
if (!fileType.isEmpty()) {
|
||||
return Optional.of(fileType);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.ofNullable(MediaUtil.getExtension(context, getUri()));
|
||||
}
|
||||
|
||||
private static @NonNull String getFileType(Optional<String> fileName) {
|
||||
if (!fileName.isPresent()) return "";
|
||||
|
||||
String[] parts = fileName.get().split("\\.");
|
||||
|
||||
if (parts.length < 2) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String suffix = parts[parts.length - 1];
|
||||
|
||||
if (suffix.length() <= 3) {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
return "";
|
||||
return MediaUtil.getFileType(context, getFileName(), getUri());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -286,6 +286,7 @@ class ShareActivity : PassphraseRequiredActivity(), MultiselectForwardFragment.C
|
||||
false,
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty()
|
||||
)
|
||||
)
|
||||
|
||||
@@ -116,6 +116,7 @@ class ShareRepository(context: Context) {
|
||||
false,
|
||||
Optional.of(Media.ALL_MEDIA_BUCKET_ID),
|
||||
Optional.empty(),
|
||||
Optional.empty(),
|
||||
Optional.empty()
|
||||
)
|
||||
}.filterNotNull()
|
||||
|
||||
@@ -359,7 +359,8 @@ object Stories {
|
||||
media.isVideoGif,
|
||||
media.bucketId,
|
||||
media.caption,
|
||||
Optional.of(transformProperties)
|
||||
Optional.of(transformProperties),
|
||||
media.fileName
|
||||
)
|
||||
}
|
||||
|
||||
@@ -398,6 +399,7 @@ object Stories {
|
||||
videoSlide.isVideoGif,
|
||||
Optional.empty(),
|
||||
videoSlide.caption,
|
||||
Optional.empty(),
|
||||
Optional.empty()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URLConnection;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class MediaUtil {
|
||||
@@ -137,6 +138,35 @@ public class MediaUtil {
|
||||
return getCorrectedMimeType(type);
|
||||
}
|
||||
|
||||
public static @NonNull Optional<String> getFileType(@NonNull Context context, Optional<String> fileName, Uri uri) {
|
||||
if (fileName.isPresent()) {
|
||||
String fileType = getFileType(fileName);
|
||||
if (!fileType.isEmpty()) {
|
||||
return Optional.of(fileType);
|
||||
}
|
||||
}
|
||||
|
||||
return Optional.ofNullable(MediaUtil.getExtension(context, uri));
|
||||
}
|
||||
|
||||
private static @NonNull String getFileType(Optional<String> fileName) {
|
||||
if (!fileName.isPresent()) return "";
|
||||
|
||||
String[] parts = fileName.get().split("\\.");
|
||||
|
||||
if (parts.length < 2) {
|
||||
return "";
|
||||
}
|
||||
|
||||
String suffix = parts[parts.length - 1];
|
||||
|
||||
if (suffix.length() <= 3) {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static @Nullable String getExtension(@NonNull Context context, @Nullable Uri uri) {
|
||||
return MimeTypeMap.getSingleton()
|
||||
.getExtensionFromMimeType(getMimeType(context, uri));
|
||||
@@ -384,6 +414,10 @@ public class MediaUtil {
|
||||
return OCTET.equals(contentType);
|
||||
}
|
||||
|
||||
public static boolean isDocumentType(String contentType) {
|
||||
return !isImageOrVideoType(contentType) && !isGif(contentType) && !isLongTextType(contentType) && !isViewOnceType(contentType);
|
||||
}
|
||||
|
||||
public static boolean hasVideoThumbnail(@NonNull Context context, @Nullable Uri uri) {
|
||||
if (uri == null) {
|
||||
return false;
|
||||
|
||||
56
app/src/main/res/layout/mediasend_document_fragment.xml
Normal file
56
app/src/main/res/layout/mediasend_document_fragment.xml
Normal file
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:viewBindingIgnore="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minWidth="70dp"
|
||||
android:minHeight="94dp"
|
||||
android:src="@drawable/ic_document_large" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/extension"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:textColor="@color/signal_light_colorOnSurface"
|
||||
style="@style/Signal.Text.Caption"
|
||||
tools:text="pdf" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="middle"
|
||||
android:textColor="@color/signal_colorOnSurface"
|
||||
android:gravity="center"
|
||||
style="@style/Signal.Text.BodyLarge"
|
||||
android:letterSpacing="0"
|
||||
tools:text="thoughts.pdf" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/size"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/signal_colorOnSurfaceVariant"
|
||||
style="@style/Signal.Text.BodyLarge"
|
||||
tools:text="12 KB" />
|
||||
|
||||
</LinearLayout>
|
||||
@@ -116,7 +116,8 @@ class MediaRepositoryTest {
|
||||
videoGif: Boolean = false,
|
||||
bucketId: Optional<String> = Optional.empty(),
|
||||
caption: Optional<String> = Optional.empty(),
|
||||
transformProperties: Optional<TransformProperties> = Optional.empty()
|
||||
transformProperties: Optional<TransformProperties> = Optional.empty(),
|
||||
fileName: Optional<String> = Optional.empty()
|
||||
): Media {
|
||||
return Media(
|
||||
uri,
|
||||
@@ -130,7 +131,8 @@ class MediaRepositoryTest {
|
||||
videoGif,
|
||||
bucketId,
|
||||
caption,
|
||||
transformProperties
|
||||
transformProperties,
|
||||
fileName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user