Add support for uploading audio attachments (#1630)

* Add support for audio attachments.
Partially addresses #1337

* Register Tusky as a target for audio sharing

* Use icon with textColorTertiary for audio preview
This commit is contained in:
Levi Bard 2020-01-16 19:05:52 +01:00 committed by Konrad Pozniak
parent a7c1345085
commit 9dccd06a06
7 changed files with 44 additions and 11 deletions

View File

@ -89,6 +89,13 @@
<data android:mimeType="video/*" /> <data android:mimeType="video/*" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="audio/*" />
</intent-filter>
<meta-data <meta-data
android:name="android.service.chooser.chooser_target_service" android:name="android.service.chooser.chooser_target_service"

View File

@ -176,7 +176,7 @@ class ComposeActivity : BaseActivity(),
* instance state will be re-queued. */ * instance state will be re-queued. */
val type = intent.type val type = intent.type
if (type != null) { if (type != null) {
if (type.startsWith("image/") || type.startsWith("video/")) { if (type.startsWith("image/") || type.startsWith("video/") || type.startsWith("audio/")) {
val uriList = ArrayList<Uri>() val uriList = ArrayList<Uri>()
if (intent.action != null) { if (intent.action != null) {
when (intent.action) { when (intent.action) {
@ -323,7 +323,7 @@ class ComposeActivity : BaseActivity(),
combineOptionalLiveData(viewModel.media, viewModel.poll) { media, poll -> combineOptionalLiveData(viewModel.media, viewModel.poll) { media, poll ->
val active = poll == null val active = poll == null
&& media!!.size != 4 && media!!.size != 4
&& media.firstOrNull()?.type != QueuedMedia.Type.VIDEO && (media.isEmpty() || media.first().type == QueuedMedia.Type.IMAGE)
enableButton(composeAddMediaButton, active, active) enableButton(composeAddMediaButton, active, active)
enablePollButton(media.isNullOrEmpty()) enablePollButton(media.isNullOrEmpty())
}.subscribe() }.subscribe()
@ -813,7 +813,7 @@ class ComposeActivity : BaseActivity(),
val intent = Intent(Intent.ACTION_GET_CONTENT) val intent = Intent(Intent.ACTION_GET_CONTENT)
intent.addCategory(Intent.CATEGORY_OPENABLE) intent.addCategory(Intent.CATEGORY_OPENABLE)
val mimeTypes = arrayOf("image/*", "video/*") val mimeTypes = arrayOf("image/*", "video/*", "audio/*")
intent.type = "*/*" intent.type = "*/*"
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
startActivityForResult(intent, MEDIA_PICK_RESULT) startActivityForResult(intent, MEDIA_PICK_RESULT)
@ -856,6 +856,9 @@ class ComposeActivity : BaseActivity(),
is VideoSizeException -> { is VideoSizeException -> {
R.string.error_video_upload_size R.string.error_video_upload_size
} }
is AudioSizeException -> {
R.string.error_audio_upload_size
}
is VideoOrImageException -> { is VideoOrImageException -> {
R.string.error_media_upload_image_or_video R.string.error_media_upload_image_or_video
} }
@ -980,7 +983,7 @@ class ComposeActivity : BaseActivity(),
val description: String? = null val description: String? = null
) { ) {
enum class Type { enum class Type {
IMAGE, VIDEO; IMAGE, VIDEO, AUDIO;
} }
} }
@ -1036,7 +1039,7 @@ class ComposeActivity : BaseActivity(),
@JvmStatic @JvmStatic
fun canHandleMimeType(mimeType: String?): Boolean { fun canHandleMimeType(mimeType: String?): Boolean {
return mimeType != null && (mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType == "text/plain") return mimeType != null && (mimeType.startsWith("image/") || mimeType.startsWith("video/") || mimeType.startsWith("audio/") || mimeType == "text/plain")
} }
} }
} }

View File

@ -127,7 +127,7 @@ class ComposeViewModel
mediaUploader.prepareMedia(uri) mediaUploader.prepareMedia(uri)
.map { (type, uri, size) -> .map { (type, uri, size) ->
val mediaItems = media.value!! val mediaItems = media.value!!
if (type == QueuedMedia.Type.VIDEO if (type != QueuedMedia.Type.IMAGE
&& mediaItems.isNotEmpty() && mediaItems.isNotEmpty()
&& mediaItems[0].type == QueuedMedia.Type.IMAGE) { && mediaItems[0].type == QueuedMedia.Type.IMAGE) {
throw VideoOrImageException() throw VideoOrImageException()
@ -388,6 +388,7 @@ class ComposeViewModel
val mediaType = when (a.type) { val mediaType = when (a.type) {
Attachment.Type.VIDEO, Attachment.Type.GIFV -> QueuedMedia.Type.VIDEO Attachment.Type.VIDEO, Attachment.Type.GIFV -> QueuedMedia.Type.VIDEO
Attachment.Type.UNKNOWN, Attachment.Type.IMAGE -> QueuedMedia.Type.IMAGE Attachment.Type.UNKNOWN, Attachment.Type.IMAGE -> QueuedMedia.Type.IMAGE
Attachment.Type.AUDIO -> QueuedMedia.Type.AUDIO
else -> QueuedMedia.Type.IMAGE else -> QueuedMedia.Type.IMAGE
} }
addUploadedMedia(a.id, mediaType, a.url.toUri(), a.description) addUploadedMedia(a.id, mediaType, a.url.toUri(), a.description)

View File

@ -69,11 +69,16 @@ class MediaPreviewAdapter(
val item = differ.currentList[position] val item = differ.currentList[position]
holder.progressImageView.setChecked(!item.description.isNullOrEmpty()) holder.progressImageView.setChecked(!item.description.isNullOrEmpty())
holder.progressImageView.setProgress(item.uploadPercent) holder.progressImageView.setProgress(item.uploadPercent)
Glide.with(holder.itemView.context) if (item.type == ComposeActivity.QueuedMedia.Type.AUDIO) {
.load(item.uri) // TODO: Fancy waveform display?
.diskCacheStrategy(DiskCacheStrategy.NONE) holder.progressImageView.setImageResource(R.drawable.ic_music_box_preview_24dp)
.dontAnimate() } else {
.into(holder.progressImageView) Glide.with(holder.itemView.context)
.load(item.uri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.dontAnimate()
.into(holder.progressImageView)
}
} }
private val differ = AsyncListDiffer(this, object : DiffUtil.ItemCallback<ComposeActivity.QueuedMedia>() { private val differ = AsyncListDiffer(this, object : DiffUtil.ItemCallback<ComposeActivity.QueuedMedia>() {

View File

@ -63,6 +63,7 @@ interface MediaUploader {
fun uploadMedia(media: QueuedMedia): Observable<UploadEvent> fun uploadMedia(media: QueuedMedia): Observable<UploadEvent>
} }
class AudioSizeException : Exception()
class VideoSizeException : Exception() class VideoSizeException : Exception()
class MediaTypeException : Exception() class MediaTypeException : Exception()
class CouldNotOpenFileException : Exception() class CouldNotOpenFileException : Exception()
@ -128,6 +129,12 @@ class MediaUploaderImpl(
"image" -> { "image" -> {
PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize) PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize)
} }
"audio" -> {
if (mediaSize > STATUS_AUDIO_SIZE_LIMIT) {
throw AudioSizeException()
}
PreparedMedia(QueuedMedia.Type.AUDIO, uri, mediaSize)
}
else -> { else -> {
throw MediaTypeException() throw MediaTypeException()
} }
@ -196,6 +203,7 @@ class MediaUploaderImpl(
private companion object { private companion object {
private const val TAG = "MediaUploaderImpl" private const val TAG = "MediaUploaderImpl"
private const val STATUS_VIDEO_SIZE_LIMIT = 41943040 // 40MiB private const val STATUS_VIDEO_SIZE_LIMIT = 41943040 // 40MiB
private const val STATUS_AUDIO_SIZE_LIMIT = 41943040 // 40MiB
private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB private const val STATUS_IMAGE_SIZE_LIMIT = 8388608 // 8MiB
private const val STATUS_IMAGE_PIXEL_SIZE_LIMIT = 16777216 // 4096^2 Pixels private const val STATUS_IMAGE_PIXEL_SIZE_LIMIT = 16777216 // 4096^2 Pixels

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="?android:textColorTertiary" android:pathData="M16,9H13V14.5A2.5,2.5 0 0,1 10.5,17A2.5,2.5 0 0,1 8,14.5A2.5,2.5 0 0,1 10.5,12C11.07,12 11.58,12.19 12,12.5V7H16M19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5A2,2 0 0,0 19,3Z" />
</vector>

View File

@ -12,6 +12,7 @@
<string name="error_compose_character_limit">The status is too long!</string> <string name="error_compose_character_limit">The status is too long!</string>
<string name="error_image_upload_size">The file must be less than 8MB.</string> <string name="error_image_upload_size">The file must be less than 8MB.</string>
<string name="error_video_upload_size">Video files must be less than 40MB.</string> <string name="error_video_upload_size">Video files must be less than 40MB.</string>
<string name="error_audio_upload_size">Audio files must be less than 40MB.</string>
<string name="error_media_upload_type">That type of file cannot be uploaded.</string> <string name="error_media_upload_type">That type of file cannot be uploaded.</string>
<string name="error_media_upload_opening">That file could not be opened.</string> <string name="error_media_upload_opening">That file could not be opened.</string>
<string name="error_media_upload_permission">Permission to read media is required.</string> <string name="error_media_upload_permission">Permission to read media is required.</string>