ComposeActivity: restore ability to upload any type of file

main
Alibek Omarov 5 years ago
parent 3e5e4329de
commit d447b683cc
  1. 51
      app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt
  2. 21
      app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeViewModel.kt
  3. 84
      app/src/main/java/com/keylesspalace/tusky/components/compose/MediaPreviewAdapter.kt
  4. 21
      app/src/main/java/com/keylesspalace/tusky/components/compose/MediaUploader.kt

@ -23,6 +23,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.ContentResolver
import android.graphics.PorterDuff import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter import android.graphics.PorterDuffColorFilter
import android.net.Uri import android.net.Uri
@ -31,6 +32,7 @@ import android.os.Bundle
import android.os.Parcelable import android.os.Parcelable
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import android.provider.MediaStore import android.provider.MediaStore
import android.provider.OpenableColumns
import android.text.TextUtils import android.text.TextUtils
import android.util.Log import android.util.Log
import android.view.KeyEvent import android.view.KeyEvent
@ -172,6 +174,35 @@ class ComposeActivity : BaseActivity(),
composeEditField.requestFocus() 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?) { private fun applyShareIntent(intent: Intent?, savedInstanceState: Bundle?) {
if (intent != null && savedInstanceState == null) { if (intent != null && savedInstanceState == null) {
/* Get incoming images being sent through a share action from another app. Only do this /* Get incoming images being sent through a share action from another app. Only do this
@ -831,9 +862,11 @@ 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/*") if(!hasNoAttachmentLimits) {
val mimeTypes = arrayOf("image/*", "video/*")
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
}
intent.type = "*/*" intent.type = "*/*"
intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes)
startActivityForResult(intent, MEDIA_PICK_RESULT) startActivityForResult(intent, MEDIA_PICK_RESULT)
} }
@ -867,8 +900,8 @@ class ComposeActivity : BaseActivity(),
} }
private fun pickMedia(uri: Uri) { private fun pickMedia(uri: Uri) {
withLifecycleContext { withLifecycleContext {
viewModel.pickMedia(uri).observe { exceptionOrItem -> viewModel.pickMedia(uri, uriToFilename(uri)).observe { exceptionOrItem ->
exceptionOrItem.asLeftOrNull()?.let { exceptionOrItem.asLeftOrNull()?.let {
val errorId = when (it) { val errorId = when (it) {
is VideoSizeException -> { is VideoSizeException -> {
@ -991,14 +1024,18 @@ class ComposeActivity : BaseActivity(),
data class QueuedMedia( data class QueuedMedia(
val localId: Long, val localId: Long,
val uri: Uri, val uri: Uri,
val type: Type, val type: Int,
val mediaSize: Long, val mediaSize: Long,
val originalFileName: String,
val noChanges: Boolean = false,
val uploadPercent: Int = 0, val uploadPercent: Int = 0,
val id: String? = null, val id: String? = null,
val description: String? = null val description: String? = null
) { ) {
enum class Type { companion object Type {
IMAGE, VIDEO; public const val IMAGE: Int = 0
public const val VIDEO: Int = 1
public const val UNKNOWN: Int = 2
} }
} }

@ -121,19 +121,20 @@ class ComposeViewModel
.autoDispose() .autoDispose()
} }
fun pickMedia(uri: Uri): LiveData<Either<Throwable, QueuedMedia>> { fun pickMedia(uri: Uri, filename: String?): LiveData<Either<Throwable, QueuedMedia>> {
// We are not calling .toLiveData() here because we don't want to stop the process when // 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). // the Activity goes away temporarily (like on screen rotation).
val liveData = MutableLiveData<Either<Throwable, QueuedMedia>>() val liveData = MutableLiveData<Either<Throwable, QueuedMedia>>()
mediaUploader.prepareMedia(uri) mediaUploader.prepareMedia(uri, instanceParams.value!!.hasNoAttachmentLimits)
.map { (type, uri, size) -> .map { (type, uri, size) ->
val mediaItems = media.value!! val mediaItems = media.value!!
if (type == QueuedMedia.Type.VIDEO if (!instanceParams.value!!.hasNoAttachmentLimits
&& type == QueuedMedia.Type.VIDEO
&& mediaItems.isNotEmpty() && mediaItems.isNotEmpty()
&& mediaItems[0].type == QueuedMedia.Type.IMAGE) { && mediaItems[0].type == QueuedMedia.Type.IMAGE) {
throw VideoOrImageException() throw VideoOrImageException()
} else { } else {
addMediaToQueue(type, uri, size) addMediaToQueue(type, uri, size, if(filename != null) filename else "unknown")
} }
} }
.subscribe({ queuedMedia -> .subscribe({ queuedMedia ->
@ -145,8 +146,9 @@ class ComposeViewModel
return liveData return liveData
} }
private fun addMediaToQueue(type: QueuedMedia.Type, uri: Uri, mediaSize: Long): QueuedMedia { private fun addMediaToQueue(type: Int, uri: Uri, mediaSize: Long, filename: String): QueuedMedia {
val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, mediaSize) val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, mediaSize, filename,
instanceParams.value!!.hasNoAttachmentLimits)
media.value = media.value!! + mediaItem media.value = media.value!! + mediaItem
mediaToDisposable[mediaItem.localId] = mediaUploader mediaToDisposable[mediaItem.localId] = mediaUploader
.uploadMedia(mediaItem) .uploadMedia(mediaItem)
@ -175,8 +177,9 @@ class ComposeViewModel
return mediaItem return mediaItem
} }
private fun addUploadedMedia(id: String, type: QueuedMedia.Type, uri: Uri, description: String?) { private fun addUploadedMedia(id: String, type: Int, uri: Uri, description: String?) {
val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, 0, -1, id, description) val mediaItem = QueuedMedia(System.currentTimeMillis(), uri, type, 0, "unknown",
instanceParams.value!!.hasNoAttachmentLimits, -1, id, description)
media.value = media.value!! + mediaItem media.value = media.value!! + mediaItem
} }
@ -379,7 +382,7 @@ class ComposeViewModel
if (loadedDraftMediaUris != null && loadedDraftMediaDescriptions != null) { if (loadedDraftMediaUris != null && loadedDraftMediaDescriptions != null) {
loadedDraftMediaUris.zip(loadedDraftMediaDescriptions) loadedDraftMediaUris.zip(loadedDraftMediaDescriptions)
.forEach { (uri, description) -> .forEach { (uri, description) ->
pickMedia(uri.toUri()).observeForever { errorOrItem -> pickMedia(uri.toUri(), null).observeForever { errorOrItem ->
if (errorOrItem.isRight() && description != null) { if (errorOrItem.isRight() && description != null) {
updateDescription(errorOrItem.asRight().localId, description) updateDescription(errorOrItem.asRight().localId, description)
} }

@ -20,6 +20,8 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.PopupMenu import android.widget.PopupMenu
import android.view.Gravity
import android.text.TextUtils
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
@ -27,13 +29,14 @@ import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.keylesspalace.tusky.R import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.components.compose.view.ProgressTextView
import com.keylesspalace.tusky.components.compose.view.ProgressImageView import com.keylesspalace.tusky.components.compose.view.ProgressImageView
class MediaPreviewAdapter( class MediaPreviewAdapter(
context: Context, context: Context,
private val onAddCaption: (ComposeActivity.QueuedMedia) -> Unit, private val onAddCaption: (ComposeActivity.QueuedMedia) -> Unit,
private val onRemove: (ComposeActivity.QueuedMedia) -> Unit private val onRemove: (ComposeActivity.QueuedMedia) -> Unit
) : RecyclerView.Adapter<MediaPreviewAdapter.PreviewViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun submitList(list: List<ComposeActivity.QueuedMedia>) { fun submitList(list: List<ComposeActivity.QueuedMedia>) {
this.differ.submitList(list) this.differ.submitList(list)
@ -61,19 +64,42 @@ class MediaPreviewAdapter(
override fun getItemCount(): Int = differ.currentList.size override fun getItemCount(): Int = differ.currentList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PreviewViewHolder { override fun getItemViewType(position: Int): Int {
return PreviewViewHolder(ProgressImageView(parent.context)) val item = differ.currentList[position]
return item.type
}
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] val item = differ.currentList[position]
holder.progressImageView.setChecked(!item.description.isNullOrEmpty())
holder.progressImageView.setProgress(item.uploadPercent) when(item.type) {
Glide.with(holder.itemView.context) ComposeActivity.QueuedMedia.Type.UNKNOWN -> {
.load(item.uri) (holder as TextViewHolder).view.setText(item.originalFileName)
.diskCacheStrategy(DiskCacheStrategy.NONE) holder.view.setChecked(!item.description.isNullOrEmpty())
.dontAnimate() holder.view.setProgress(item.uploadPercent)
.into(holder.progressImageView) }
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<ComposeActivity.QueuedMedia>() { private val differ = AsyncListDiffer(this, object : DiffUtil.ItemCallback<ComposeActivity.QueuedMedia>() {
@ -86,8 +112,32 @@ class MediaPreviewAdapter(
} }
}) })
inner class PreviewViewHolder(val progressImageView: ProgressImageView) inner class TextViewHolder(val view: ProgressTextView)
: RecyclerView.ViewHolder(progressImageView) { : 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 view: ProgressImageView)
: RecyclerView.ViewHolder(view) {
init { init {
val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize) val layoutParams = ConstraintLayout.LayoutParams(thumbnailViewSize, thumbnailViewSize)
val margin = itemView.context.resources val margin = itemView.context.resources
@ -95,10 +145,10 @@ class MediaPreviewAdapter(
val marginBottom = itemView.context.resources val marginBottom = itemView.context.resources
.getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom) .getDimensionPixelSize(R.dimen.compose_media_preview_margin_bottom)
layoutParams.setMargins(margin, 0, margin, marginBottom) layoutParams.setMargins(margin, 0, margin, marginBottom)
progressImageView.layoutParams = layoutParams view.layoutParams = layoutParams
progressImageView.scaleType = ImageView.ScaleType.CENTER_CROP view.scaleType = ImageView.ScaleType.CENTER_CROP
progressImageView.setOnClickListener { view.setOnClickListener {
onMediaClick(adapterPosition, progressImageView) onMediaClick(adapterPosition, view)
} }
} }
} }

@ -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 { interface MediaUploader {
fun prepareMedia(inUri: Uri): Single<PreparedMedia> fun prepareMedia(inUri: Uri, hasNoLimits: Boolean): Single<PreparedMedia>
fun uploadMedia(media: QueuedMedia): Observable<UploadEvent> fun uploadMedia(media: QueuedMedia): Observable<UploadEvent>
} }
@ -83,7 +83,7 @@ class MediaUploaderImpl(
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
} }
override fun prepareMedia(inUri: Uri): Single<PreparedMedia> { override fun prepareMedia(inUri: Uri, hasNoLimits: Boolean): Single<PreparedMedia> {
return Single.fromCallable { return Single.fromCallable {
var mediaSize = getMediaSize(contentResolver, inUri) var mediaSize = getMediaSize(contentResolver, inUri)
var uri = inUri var uri = inUri
@ -120,7 +120,7 @@ class MediaUploaderImpl(
val topLevelType = mimeType.substring(0, mimeType.indexOf('/')) val topLevelType = mimeType.substring(0, mimeType.indexOf('/'))
when (topLevelType) { when (topLevelType) {
"video" -> { "video" -> {
if (mediaSize > STATUS_VIDEO_SIZE_LIMIT) { if (!hasNoLimits && mediaSize > STATUS_VIDEO_SIZE_LIMIT) {
throw VideoSizeException() throw VideoSizeException()
} }
PreparedMedia(QueuedMedia.Type.VIDEO, uri, mediaSize) PreparedMedia(QueuedMedia.Type.VIDEO, uri, mediaSize)
@ -129,7 +129,8 @@ class MediaUploaderImpl(
PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize) PreparedMedia(QueuedMedia.Type.IMAGE, uri, mediaSize)
} }
else -> { else -> {
throw MediaTypeException() PreparedMedia(QueuedMedia.Type.UNKNOWN, uri, mediaSize)
// throw MediaTypeException()
} }
} }
} else { } else {
@ -146,10 +147,10 @@ class MediaUploaderImpl(
val map = MimeTypeMap.getSingleton() val map = MimeTypeMap.getSingleton()
val fileExtension = map.getExtensionFromMimeType(mimeType) val fileExtension = map.getExtensionFromMimeType(mimeType)
val filename = String.format("%s_%s_%s.%s", val filename = String.format("%s_%s_%s.%s",
context.getString(R.string.app_name), context.getString(R.string.app_name),
Date().time.toString(), Date().time.toString(),
randomAlphanumericString(10), randomAlphanumericString(10),
fileExtension) fileExtension)
val stream = contentResolver.openInputStream(media.uri) val stream = contentResolver.openInputStream(media.uri)
@ -188,7 +189,7 @@ class MediaUploaderImpl(
} }
private fun shouldResizeMedia(media: QueuedMedia): Boolean { 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 && (media.mediaSize > STATUS_IMAGE_SIZE_LIMIT
|| getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT) || getImageSquarePixels(context.contentResolver, media.uri) > STATUS_IMAGE_PIXEL_SIZE_LIMIT)
} }

Loading…
Cancel
Save