From d447b683cc36ba17bea87ca0f1647d9bbedccd6b Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Sat, 11 Jan 2020 07:47:11 +0300 Subject: [PATCH] ComposeActivity: restore ability to upload any type of file --- .../components/compose/ComposeActivity.kt | 51 +++++++++-- .../components/compose/ComposeViewModel.kt | 23 ++--- .../components/compose/MediaPreviewAdapter.kt | 86 +++++++++++++++---- .../tusky/components/compose/MediaUploader.kt | 23 ++--- 4 files changed, 137 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index 322cb176..4787aee2 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -23,6 +23,7 @@ import android.content.Context import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager +import android.content.ContentResolver import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.net.Uri @@ -31,6 +32,7 @@ import android.os.Bundle import android.os.Parcelable import androidx.preference.PreferenceManager import android.provider.MediaStore +import android.provider.OpenableColumns import android.text.TextUtils import android.util.Log import android.view.KeyEvent @@ -171,6 +173,35 @@ class ComposeActivity : BaseActivity(), composeEditField.requestFocus() } + + private fun uriToFilename(uri: Uri): String { + var result: String = "unknown" + if(uri.scheme.equals("content")) { + val cursor = contentResolver.query(uri, null, null, null, null) + if(cursor != null) { + try { + if(cursor.moveToFirst()) { + result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)) + } + } + finally { + cursor.close() + } + } + } + if(result.equals("unknown")) { + val path = uri.getPath() + if(path != null) { + result = path + val cut = result.lastIndexOf('/') + if (cut != -1) { + result = result.substring(cut + 1) + } + } + } + return result + } + private fun applyShareIntent(intent: Intent?, savedInstanceState: Bundle?) { if (intent != null && savedInstanceState == null) { @@ -831,9 +862,11 @@ class ComposeActivity : BaseActivity(), val intent = Intent(Intent.ACTION_GET_CONTENT) intent.addCategory(Intent.CATEGORY_OPENABLE) - val mimeTypes = arrayOf("image/*", "video/*") + if(!hasNoAttachmentLimits) { + val mimeTypes = arrayOf("image/*", "video/*") + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) + } intent.type = "*/*" - intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes) startActivityForResult(intent, MEDIA_PICK_RESULT) } @@ -867,8 +900,8 @@ class ComposeActivity : BaseActivity(), } private fun pickMedia(uri: Uri) { - withLifecycleContext { - viewModel.pickMedia(uri).observe { exceptionOrItem -> + withLifecycleContext { + viewModel.pickMedia(uri, uriToFilename(uri)).observe { exceptionOrItem -> exceptionOrItem.asLeftOrNull()?.let { val errorId = when (it) { is VideoSizeException -> { @@ -991,14 +1024,18 @@ class ComposeActivity : BaseActivity(), data class QueuedMedia( val localId: Long, val uri: Uri, - val type: Type, + val type: Int, val mediaSize: Long, + val originalFileName: String, + val noChanges: Boolean = false, val uploadPercent: Int = 0, val id: String? = null, val description: String? = null ) { - enum class Type { - IMAGE, VIDEO; + companion object Type { + public const val IMAGE: Int = 0 + public const val VIDEO: Int = 1 + public const val UNKNOWN: Int = 2 } } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt index de210fbd..3f153124 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt @@ -121,19 +121,20 @@ class ComposeViewModel .autoDispose() } - fun pickMedia(uri: Uri): LiveData> { + fun pickMedia(uri: Uri, filename: String?): LiveData> { // We are not calling .toLiveData() here because we don't want to stop the process when // the Activity goes away temporarily (like on screen rotation). val liveData = MutableLiveData>() - mediaUploader.prepareMedia(uri) + mediaUploader.prepareMedia(uri, instanceParams.value!!.hasNoAttachmentLimits) .map { (type, uri, size) -> val mediaItems = media.value!! - if (type == QueuedMedia.Type.VIDEO + if (!instanceParams.value!!.hasNoAttachmentLimits + && type == QueuedMedia.Type.VIDEO && mediaItems.isNotEmpty() && mediaItems[0].type == QueuedMedia.Type.IMAGE) { throw VideoOrImageException() } else { - addMediaToQueue(type, uri, size) + addMediaToQueue(type, uri, size, if(filename != null) filename else "unknown") } } .subscribe({ queuedMedia -> @@ -144,9 +145,10 @@ class ComposeViewModel .autoDispose() return liveData } - - private fun addMediaToQueue(type: QueuedMedia.Type, uri: Uri, mediaSize: Long): QueuedMedia { - val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, mediaSize) + + private fun addMediaToQueue(type: Int, uri: Uri, mediaSize: Long, filename: String): QueuedMedia { + val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, mediaSize, filename, + instanceParams.value!!.hasNoAttachmentLimits) media.value = media.value!! + mediaItem mediaToDisposable[mediaItem.localId] = mediaUploader .uploadMedia(mediaItem) @@ -175,8 +177,9 @@ class ComposeViewModel return mediaItem } - private fun addUploadedMedia(id: String, type: QueuedMedia.Type, uri: Uri, description: String?) { - val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, 0, -1, id, description) + private fun addUploadedMedia(id: String, type: Int, uri: Uri, description: String?) { + val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, 0, "unknown", + instanceParams.value!!.hasNoAttachmentLimits, -1, id, description) media.value = media.value!! + mediaItem } @@ -379,7 +382,7 @@ class ComposeViewModel if (loadedDraftMediaUris != null && loadedDraftMediaDescriptions != null) { loadedDraftMediaUris.zip(loadedDraftMediaDescriptions) .forEach { (uri, description) -> - pickMedia(uri.toUri()).observeForever { errorOrItem -> + pickMedia(uri.toUri(), null).observeForever { errorOrItem -> if (errorOrItem.isRight() && description != null) { updateDescription(errorOrItem.asRight().localId, description) } diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt index babb0a39..54156c65 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt @@ -20,6 +20,8 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageView import android.widget.PopupMenu +import android.view.Gravity +import android.text.TextUtils import androidx.constraintlayout.widget.ConstraintLayout import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.DiffUtil @@ -27,13 +29,14 @@ import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.keylesspalace.tusky.R +import com.keylesspalace.tusky.components.compose.view.ProgressTextView import com.keylesspalace.tusky.components.compose.view.ProgressImageView class MediaPreviewAdapter( context: Context, private val onAddCaption: (ComposeActivity.QueuedMedia) -> Unit, private val onRemove: (ComposeActivity.QueuedMedia) -> Unit -) : RecyclerView.Adapter() { +) : RecyclerView.Adapter() { fun submitList(list: List) { this.differ.submitList(list) @@ -60,20 +63,43 @@ class MediaPreviewAdapter( context.resources.getDimensionPixelSize(R.dimen.compose_media_preview_size) override fun getItemCount(): Int = differ.currentList.size + + override fun getItemViewType(position: Int): Int { + val item = differ.currentList[position] + return item.type + } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PreviewViewHolder { - return PreviewViewHolder(ProgressImageView(parent.context)) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + when(viewType) { + ComposeActivity.QueuedMedia.Type.UNKNOWN -> { + return TextViewHolder(ProgressTextView(parent.context)) + } + else -> { + return PreviewViewHolder(ProgressImageView(parent.context)) + } + } } - override fun onBindViewHolder(holder: PreviewViewHolder, position: Int) { + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val item = differ.currentList[position] - holder.progressImageView.setChecked(!item.description.isNullOrEmpty()) - holder.progressImageView.setProgress(item.uploadPercent) - Glide.with(holder.itemView.context) - .load(item.uri) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .dontAnimate() - .into(holder.progressImageView) + + when(item.type) { + ComposeActivity.QueuedMedia.Type.UNKNOWN -> { + (holder as TextViewHolder).view.setText(item.originalFileName) + holder.view.setChecked(!item.description.isNullOrEmpty()) + holder.view.setProgress(item.uploadPercent) + } + else -> { + (holder as PreviewViewHolder).view.setChecked(!item.description.isNullOrEmpty()) + holder.view.setProgress(item.uploadPercent) + + Glide.with(holder.itemView.context) + .load(item.uri) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .dontAnimate() + .into(holder.view) + } + } } private val differ = AsyncListDiffer(this, object : DiffUtil.ItemCallback() { @@ -85,9 +111,33 @@ class MediaPreviewAdapter( return oldItem == newItem } }) + + inner class TextViewHolder(val view: ProgressTextView) + : RecyclerView.ViewHolder(view) { + init { + val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize) + val margin = itemView.context.resources + .getDimensionPixelSize(R.dimen.compose_media_preview_margin) + val marginBottom = itemView.context.resources + .getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom) + layoutParams.setMargins(margin, 0, margin, marginBottom) + view.layoutParams = layoutParams + view.gravity = Gravity.CENTER + view.setHorizontallyScrolling(true) + view.ellipsize = TextUtils.TruncateAt.MARQUEE + view.marqueeRepeatLimit = -1 + view.setSingleLine() + view.setSelected(true) + view.maxLines = 1 + view.textSize = 16.0f + view.setOnClickListener { + onMediaClick(adapterPosition, view) + } + } + } - inner class PreviewViewHolder(val progressImageView: ProgressImageView) - : RecyclerView.ViewHolder(progressImageView) { + inner class PreviewViewHolder(val view: ProgressImageView) + : RecyclerView.ViewHolder(view) { init { val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize) val margin = itemView.context.resources @@ -95,11 +145,11 @@ class MediaPreviewAdapter( val marginBottom = itemView.context.resources .getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom) layoutParams.setMargins(margin, 0, margin, marginBottom) - progressImageView.layoutParams = layoutParams - progressImageView.scaleType = ImageView.ScaleType.CENTER_CROP - progressImageView.setOnClickListener { - onMediaClick(adapterPosition, progressImageView) + view.layoutParams = layoutParams + view.scaleType = ImageView.ScaleType.CENTER_CROP + view.setOnClickListener { + onMediaClick(adapterPosition, view) } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt index af41f4bb..10eb30b5 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt @@ -56,10 +56,10 @@ fun createNewImageFile(context: Context): File { ) } -data class PreparedMedia(val type: QueuedMedia.Type, val uri: Uri, val size: Long) +data class PreparedMedia(val type: Int, val uri: Uri, val size: Long) interface MediaUploader { - fun prepareMedia(inUri: Uri): Single + fun prepareMedia(inUri: Uri, hasNoLimits: Boolean): Single fun uploadMedia(media: QueuedMedia): Observable } @@ -83,7 +83,7 @@ class MediaUploaderImpl( .subscribeOn(Schedulers.io()) } - override fun prepareMedia(inUri: Uri): Single { + override fun prepareMedia(inUri: Uri, hasNoLimits: Boolean): Single { return Single.fromCallable { var mediaSize = getMediaSize(contentResolver, inUri) var uri = inUri @@ -120,7 +120,7 @@ class MediaUploaderImpl( val topLevelType = mimeType.substring(0, mimeType.indexOf('/')) when (topLevelType) { "video" -> { - if (mediaSize > STATUS_VIDEO_SIZE_LIMIT) { + if (!hasNoLimits && mediaSize > STATUS_VIDEO_SIZE_LIMIT) { throw VideoSizeException() } PreparedMedia(QueuedMedia.Type.VIDEO, uri, mediaSize) @@ -129,7 +129,8 @@ class MediaUploaderImpl( PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize) } else -> { - throw MediaTypeException() + PreparedMedia(QueuedMedia.Type.UNKNOWN, uri, mediaSize) + // throw MediaTypeException() } } } else { @@ -139,17 +140,17 @@ class MediaUploaderImpl( } private val contentResolver = context.contentResolver - + private fun upload(media: QueuedMedia): Observable { return Observable.create { emitter -> var mimeType = contentResolver.getType(media.uri) val map = MimeTypeMap.getSingleton() val fileExtension = map.getExtensionFromMimeType(mimeType) val filename = String.format("%s_%s_%s.%s", - context.getString(R.string.app_name), - Date().time.toString(), - randomAlphanumericString(10), - fileExtension) + context.getString(R.string.app_name), + Date().time.toString(), + randomAlphanumericString(10), + fileExtension) val stream = contentResolver.openInputStream(media.uri) @@ -188,7 +189,7 @@ class MediaUploaderImpl( } private fun shouldResizeMedia(media: QueuedMedia): Boolean { - return media.type == QueuedMedia.Type.IMAGE + return !media.noChanges && media.type == QueuedMedia.Type.IMAGE && (media.mediaSize > STATUS_IMAGE_SIZE_LIMIT || getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT) }