forked from GitHub-Mirror/riotX-android
186 lines
6.9 KiB
Kotlin
186 lines
6.9 KiB
Kotlin
/*
|
|
* Copyright 2019 New Vector Ltd
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package im.vector.riotx.features.media
|
|
|
|
import android.graphics.drawable.Drawable
|
|
import android.net.Uri
|
|
import android.os.Parcelable
|
|
import android.widget.ImageView
|
|
import androidx.exifinterface.media.ExifInterface
|
|
import com.bumptech.glide.load.DataSource
|
|
import com.bumptech.glide.load.engine.GlideException
|
|
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
|
import com.bumptech.glide.request.RequestListener
|
|
import com.bumptech.glide.request.target.Target
|
|
import com.github.piasy.biv.view.BigImageView
|
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
|
import im.vector.matrix.android.internal.crypto.attachments.ElementToDecrypt
|
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
|
import im.vector.riotx.core.glide.GlideApp
|
|
import im.vector.riotx.core.glide.GlideRequest
|
|
import im.vector.riotx.core.utils.DimensionUtils.dpToPx
|
|
import kotlinx.android.parcel.Parcelize
|
|
import timber.log.Timber
|
|
import java.io.File
|
|
import javax.inject.Inject
|
|
|
|
class ImageContentRenderer @Inject constructor(private val activeSessionHolder: ActiveSessionHolder) {
|
|
|
|
@Parcelize
|
|
data class Data(
|
|
val eventId: String,
|
|
val filename: String,
|
|
val url: String?,
|
|
val elementToDecrypt: ElementToDecrypt?,
|
|
val height: Int?,
|
|
val maxHeight: Int,
|
|
val width: Int?,
|
|
val maxWidth: Int,
|
|
val orientation: Int? = null,
|
|
val rotation: Int? = null
|
|
) : Parcelable {
|
|
|
|
fun isLocalFile(): Boolean {
|
|
return url != null && File(url).exists()
|
|
}
|
|
}
|
|
|
|
enum class Mode {
|
|
FULL_SIZE,
|
|
THUMBNAIL
|
|
}
|
|
|
|
fun render(data: Data, mode: Mode, imageView: ImageView) {
|
|
val (width, height) = processSize(data, mode)
|
|
imageView.layoutParams.height = height
|
|
imageView.layoutParams.width = width
|
|
|
|
createGlideRequest(data, mode, imageView, width, height)
|
|
.dontAnimate()
|
|
.transform(RoundedCorners(dpToPx(8, imageView.context)))
|
|
.thumbnail(0.3f)
|
|
.into(imageView)
|
|
|
|
}
|
|
|
|
fun renderFitTarget(data: Data, mode: Mode, imageView: ImageView, callback: ((Boolean) -> Unit)? = null) {
|
|
val (width, height) = processSize(data, mode)
|
|
|
|
createGlideRequest(data, mode, imageView, width, height)
|
|
.listener(object : RequestListener<Drawable> {
|
|
override fun onLoadFailed(e: GlideException?,
|
|
model: Any?,
|
|
target: Target<Drawable>?,
|
|
isFirstResource: Boolean): Boolean {
|
|
callback?.invoke(false)
|
|
return false
|
|
}
|
|
|
|
override fun onResourceReady(resource: Drawable?,
|
|
model: Any?,
|
|
target: Target<Drawable>?,
|
|
dataSource: DataSource?,
|
|
isFirstResource: Boolean): Boolean {
|
|
callback?.invoke(true)
|
|
return false
|
|
}
|
|
|
|
})
|
|
.fitCenter()
|
|
.into(imageView)
|
|
}
|
|
|
|
private fun createGlideRequest(data: Data, mode: Mode, imageView: ImageView, width: Int, height: Int): GlideRequest<Drawable> {
|
|
return if (data.elementToDecrypt != null) {
|
|
// Encrypted image
|
|
GlideApp
|
|
.with(imageView)
|
|
.load(data)
|
|
} else {
|
|
// Clear image
|
|
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
|
val resolvedUrl = when (mode) {
|
|
Mode.FULL_SIZE -> contentUrlResolver.resolveFullSize(data.url)
|
|
Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
|
}
|
|
//Fallback to base url
|
|
?: data.url
|
|
|
|
GlideApp
|
|
.with(imageView)
|
|
.load(resolvedUrl)
|
|
}
|
|
}
|
|
|
|
fun render(data: Data, imageView: BigImageView) {
|
|
val (width, height) = processSize(data, Mode.THUMBNAIL)
|
|
val contentUrlResolver = activeSessionHolder.getActiveSession().contentUrlResolver()
|
|
val fullSize = contentUrlResolver.resolveFullSize(data.url)
|
|
val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
|
|
|
if (fullSize.isNullOrBlank() || thumbnail.isNullOrBlank()) {
|
|
Timber.w("Invalid urls")
|
|
return
|
|
}
|
|
|
|
imageView.showImage(
|
|
Uri.parse(thumbnail),
|
|
Uri.parse(fullSize)
|
|
)
|
|
}
|
|
|
|
private fun processSize(data: Data, mode: Mode): Pair<Int, Int> {
|
|
val maxImageWidth = data.maxWidth
|
|
val maxImageHeight = data.maxHeight
|
|
val rotationAngle = data.rotation ?: 0
|
|
val orientation = data.orientation ?: ExifInterface.ORIENTATION_NORMAL
|
|
var width = data.width ?: maxImageWidth
|
|
var height = data.height ?: maxImageHeight
|
|
var finalHeight = -1
|
|
var finalWidth = -1
|
|
|
|
// if the image size is known
|
|
// compute the expected height
|
|
if (width > 0 && height > 0) {
|
|
// swap width and height if the image is side oriented
|
|
if (rotationAngle == 90 || rotationAngle == 270) {
|
|
val tmp = width
|
|
width = height
|
|
height = tmp
|
|
} else if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) {
|
|
val tmp = width
|
|
width = height
|
|
height = tmp
|
|
}
|
|
if (mode == Mode.FULL_SIZE) {
|
|
finalHeight = height
|
|
finalWidth = width
|
|
} else {
|
|
finalHeight = Math.min(maxImageWidth * height / width, maxImageHeight)
|
|
finalWidth = finalHeight * width / height
|
|
}
|
|
}
|
|
// ensure that some values are properly initialized
|
|
if (finalHeight < 0) {
|
|
finalHeight = maxImageHeight
|
|
}
|
|
if (finalWidth < 0) {
|
|
finalWidth = maxImageWidth
|
|
}
|
|
return Pair(finalWidth, finalHeight)
|
|
}
|
|
} |