Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ import android.provider.MediaStore
import android.util.Size
import android.view.WindowManager
import android.widget.ImageView
import androidx.core.content.ContextCompat
import com.nextcloud.client.account.User
import com.nextcloud.utils.extensions.getBigThumbnail
import com.nextcloud.utils.extensions.getBigThumbnailKey
import com.nextcloud.utils.extensions.getSmallThumbnail
import com.nextcloud.utils.extensions.isPNG
import com.nextcloud.utils.extensions.setMediaThumbnail
import com.nextcloud.utils.extensions.toFile
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.ThumbnailsCacheManager
Expand Down Expand Up @@ -98,43 +97,40 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag
return
}

var newImage = false
val bitmap: Bitmap? = getBitmap(file, onNewThumbnail = { newImage = true })
val bitmap: Bitmap? = getBitmap(file)

if (bitmap == null) {
withContext(Dispatchers.Main) { listener.onError() }
return
}

setThumbnail(bitmap, file, imageView, newImage, listener)
setThumbnail(bitmap, file, imageView, listener)
} catch (_: Exception) {
withContext(Dispatchers.Main) { listener.onError() }
}
}

private suspend fun getBitmap(file: OCFile, onNewThumbnail: () -> Unit): Bitmap? = withContext(Dispatchers.IO) {
private suspend fun getBitmap(file: OCFile): Bitmap? = withContext(Dispatchers.IO) {
val cached = file.getBigThumbnail()
if (cached != null && !file.isUpdateThumbnailNeeded) {
return@withContext applyVideoOverlayIfNeeded(file, cached)
return@withContext cached
}

if (file.isDown) {
val local = decodeLocalThumbnail(file)
if (local != null) {
ThumbnailsCacheManager.addBitmapToCache(file.getBigThumbnailKey(), local)
onNewThumbnail()
return@withContext applyVideoOverlayIfNeeded(file, local)
return@withContext local
}
}

val remote = semaphore.withPermit { fetchFromServer(file) }
if (remote != null) {
onNewThumbnail()
return@withContext applyVideoOverlayIfNeeded(file, remote)
return@withContext remote
}

file.getSmallThumbnail()?.let { small ->
return@withContext applyVideoOverlayIfNeeded(file, small)
return@withContext small
}

null
Expand Down Expand Up @@ -196,34 +192,16 @@ class GalleryImageGenerationJob(private val user: User, private val storageManag
null
}

private fun applyVideoOverlayIfNeeded(file: OCFile, bitmap: Bitmap): Bitmap = if (MimeTypeUtil.isVideo(file)) {
ThumbnailsCacheManager.addVideoOverlay(bitmap, MainApp.getAppContext())
} else {
bitmap
}

private suspend fun setThumbnail(
bitmap: Bitmap,
file: OCFile,
imageView: ImageView,
newImage: Boolean,
listener: GalleryImageGenerationListener
) = withContext(Dispatchers.Main) {
val tagId = file.fileId.toString()

if (imageView.tag.toString() == tagId) {
if (file.isPNG()) {
imageView.setBackgroundColor(
ContextCompat.getColor(MainApp.getAppContext(), R.color.bg_default)
)
}

if (newImage) listener.onNewGalleryImage()

if (imageView.isAttachedToWindow) {
imageView.setImageBitmap(bitmap)
imageView.invalidate()
}
if (imageView.tag.toString() == tagId && imageView.isAttachedToWindow) {
imageView.setMediaThumbnail(file, bitmap)
}

listener.onSuccess()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,5 @@ package com.nextcloud.client.jobs.gallery

interface GalleryImageGenerationListener {
fun onSuccess()
fun onNewGalleryImage()
fun onError()
}
20 changes: 3 additions & 17 deletions app/src/main/java/com/nextcloud/utils/OCFileUtils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,15 @@
*/
package com.nextcloud.utils

import android.graphics.Color
import android.graphics.drawable.Drawable
import android.util.LruCache
import android.graphics.drawable.BitmapDrawable
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
import com.nextcloud.utils.extensions.getBitmapSize
import com.nextcloud.utils.extensions.getExifSize
import com.owncloud.android.MainApp
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.lib.common.utils.Log_OC
import com.owncloud.android.utils.BitmapUtils
import com.owncloud.android.utils.MimeTypeUtil

@Suppress("TooGenericExceptionCaught", "ReturnCount")
Expand Down Expand Up @@ -57,7 +53,7 @@ object OCFileUtils {
return fallbackPair
}

fun getMediaPlaceholder(file: OCFile, imageDimension: Pair<Int, Int>): BitmapDrawable {
fun getMediaPlaceholder(file: OCFile): Drawable? {
val context = MainApp.getAppContext()

val drawableId = if (MimeTypeUtil.isImage(file)) {
Expand All @@ -68,16 +64,6 @@ object OCFileUtils {
R.drawable.file
}

val drawable = ContextCompat.getDrawable(context, drawableId)
?: return Color.GRAY.toDrawable().toBitmap(imageDimension.first, imageDimension.second)
.toDrawable(context.resources)

val bitmap = BitmapUtils.drawableToBitmap(
drawable,
imageDimension.first,
imageDimension.second
)

return bitmap.toDrawable(context.resources)
return ContextCompat.getDrawable(context, drawableId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@
package com.nextcloud.utils.extensions

import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.GradientDrawable
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.view.ViewOutlineProvider
import android.widget.ImageView
import androidx.annotation.ColorInt
import androidx.annotation.DrawableRes
import androidx.core.content.ContextCompat
import com.elyeproj.loaderviewlibrary.LoaderImageView
import com.nextcloud.utils.OCFileUtils
import com.owncloud.android.R
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.utils.MimeTypeUtil

fun ImageView.startShimmer(shimmer: LoaderImageView) {
shimmer.setImageResource(R.drawable.background)
Expand All @@ -33,6 +38,44 @@ fun ImageView.stopShimmer(shimmer: LoaderImageView?) {
setVisibility(View.VISIBLE)
}

fun ImageView.setMediaPlaceholder(file: OCFile, iconInset: Int) {
scaleType = ImageView.ScaleType.FIT_CENTER
setPadding(iconInset, iconInset, iconInset, iconInset)
setBackgroundResource(R.color.media_placeholder_background)
foreground = null
setImageDrawable(OCFileUtils.getMediaPlaceholder(file))
setTag(R.id.media_thumbnail_file_id, null)
}

fun ImageView.setMediaThumbnail(file: OCFile, bitmap: Bitmap) {
scaleType = ImageView.ScaleType.CENTER_CROP
setPadding(0, 0, 0, 0)

if (file.isPNG()) {
setBackgroundResource(R.color.bg_default)
} else {
background = null
}

applyVideoOverlay(file)

setImageBitmap(bitmap)
setTag(R.id.media_thumbnail_file_id, file.fileId)
}

private fun ImageView.applyVideoOverlay(file: OCFile) {
if (!MimeTypeUtil.isVideo(file)) {
foreground = null
return
}

// without a gravity the foreground is stretched over the whole cell, which distorts the icon
foregroundGravity = Gravity.CENTER
foreground = ContextCompat.getDrawable(context, R.drawable.video_white)
}

fun ImageView.showsMediaThumbnailOf(file: OCFile): Boolean = getTag(R.id.media_thumbnail_file_id) == file.fileId

@JvmOverloads
fun ImageView.makeRoundedWithIcon(
context: Context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ package com.nextcloud.utils.extensions
import com.owncloud.android.MainApp
import com.owncloud.android.datamodel.GalleryItems
import com.owncloud.android.datamodel.GalleryRow
import com.owncloud.android.datamodel.GalleryRowLayout
import com.owncloud.android.datamodel.OCFile
import com.owncloud.android.datamodel.OCFileDepth
import com.owncloud.android.datamodel.OCFileDepth.DeepLevel
Expand Down Expand Up @@ -56,7 +57,7 @@ fun OCFile?.getDepth(): OCFileDepth? {
return DeepLevel
}

fun List<OCFile>.toGalleryItems(columns: Int, defaultSize: Int): List<GalleryItems> {
fun List<OCFile>.toGalleryItems(layout: GalleryRowLayout): List<GalleryItems> {
if (isEmpty()) return emptyList()

val calendar = Calendar.getInstance()
Expand All @@ -70,18 +71,18 @@ fun List<OCFile>.toGalleryItems(columns: Int, defaultSize: Int): List<GalleryIte
calendar.timeInMillis
}
.map { (date, filesList) ->
GalleryItems(date, transformToRows(filesList, columns, defaultSize))
GalleryItems(date, transformToRows(filesList, layout))
}
.sortedByDescending { it.date }
}

private fun transformToRows(list: List<OCFile>, columns: Int, defaultSize: Int): List<GalleryRow> {
private fun transformToRows(list: List<OCFile>, layout: GalleryRowLayout): List<GalleryRow> {
if (list.isEmpty()) return emptyList()

return list
.sortedByDescending { it.modificationTimestamp }
.chunked(columns)
.map { chunk -> GalleryRow(chunk, defaultSize, defaultSize) }
.chunked(layout.columns)
.map { chunk -> GalleryRow(chunk, layout.measure(chunk)) }
}

fun OCFile.toEncryptionEvent(encrypt: Boolean): EncryptionEvent = EncryptionEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import com.nextcloud.utils.extensions.getSmallThumbnailKey
import com.nextcloud.utils.extensions.startShimmer
import com.nextcloud.utils.extensions.stopShimmer
import com.nextcloud.utils.extensions.toFile
import com.nextcloud.utils.extensions.videoOverlayKey
import com.owncloud.android.R
import com.owncloud.android.datamodel.FileDataStorageManager
import com.owncloud.android.datamodel.OCFile
Expand Down Expand Up @@ -69,19 +68,15 @@ class FileThumbnailGenerator @Inject constructor(
)

private val tasks = CopyOnWriteArrayList<ThumbnailGenerationTask>()
private val placeholders = mutableMapOf<String, Bitmap>()

fun setThumbnail(file: OCFile, view: ImageView, arguments: ThumbnailArguments) {
if (file.remoteId == null) {
setLocalThumbnail(file, view, arguments)
return
}

if (!file.isPreviewAvailable) {
generate(file, view, arguments)
return
}

val cached = ThumbnailMemoryCache.get(file.getSmallThumbnailKey())
val cached = ThumbnailsCacheManager.getBitmapFromDiskCache(file.getSmallThumbnailKey())
if (cached == null || file.isUpdateThumbnailNeeded) {
generate(file, view, arguments)
} else {
Expand All @@ -91,6 +86,16 @@ class FileThumbnailGenerator @Inject constructor(
applyPngBackground(file, view)
}

private fun show(bitmap: Bitmap, file: OCFile, view: ImageView, arguments: ThumbnailArguments) {
view.stopShimmer(arguments.shimmer)

if (MimeTypeUtil.isVideo(file) && !arguments.hideVideoOverlay) {
view.setImageBitmap(VideoOverlayGenerator.addOverlay(bitmap, context))
} else {
BitmapUtils.setRoundedBitmapAccordingToListType(arguments.isGrid, bitmap, view)
}
}

fun setOfflineOperationThumbnail(file: OCFile, view: ImageView) {
if (file.isFolder) {
view.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.ic_folder_offline))
Expand All @@ -117,25 +122,6 @@ class FileThumbnailGenerator @Inject constructor(
tasks.clear()
}

private fun show(bitmap: Bitmap, file: OCFile, view: ImageView, arguments: ThumbnailArguments) {
view.stopShimmer(arguments.shimmer)

if (MimeTypeUtil.isVideo(file) && !arguments.hideVideoOverlay) {
view.setImageBitmap(file.withVideoOverlay(bitmap))
} else {
BitmapUtils.setRoundedBitmapAccordingToListType(arguments.isGrid, bitmap, view)
}
}

private fun OCFile.withVideoOverlay(thumbnail: Bitmap): Bitmap {
val overlayKey = videoOverlayKey(getSmallThumbnailKey())
ThumbnailMemoryCache.get(overlayKey)?.let { return it }

return ThumbnailsCacheManager.addVideoOverlay(thumbnail, context).also {
ThumbnailMemoryCache.put(overlayKey, it)
}
}

private fun setLocalThumbnail(file: OCFile, view: ImageView, arguments: ThumbnailArguments) {
val localFile = file.storagePath.toFile()

Expand All @@ -159,12 +145,6 @@ class FileThumbnailGenerator @Inject constructor(
return
}

val cached = ThumbnailMemoryCache.get(file.getSmallThumbnailKey())
if (cached != null) {
show(cached, file, view, arguments)
return
}

tasks.removeIf { it.isCancelled || it.status == AsyncTask.Status.FINISHED }

startTask(
Expand Down Expand Up @@ -244,13 +224,15 @@ class FileThumbnailGenerator @Inject constructor(

private fun OCFile.mimeIcon(): Drawable? = MimeTypeUtil.getFileTypeIcon(mimeType, fileName, context, viewThemeUtils)

private fun OCFile.placeholder(): Bitmap {
val drawable = mimeIcon()
?: ResourcesCompat.getDrawable(context.resources, R.drawable.file_image, null)
?: Color.GRAY.toDrawable()
val size = ThumbnailsCacheManager.getThumbnailDimension()
private fun OCFile.placeholder(): Bitmap = synchronized(placeholders) {
placeholders.getOrPut(mimeType.orEmpty()) {
val drawable = mimeIcon()
?: ResourcesCompat.getDrawable(context.resources, R.drawable.file_image, null)
?: Color.GRAY.toDrawable()
val size = ThumbnailsCacheManager.getThumbnailDimension()

return BitmapUtils.drawableToBitmap(drawable, size, size)
BitmapUtils.drawableToBitmap(drawable, size, size)
}
}

private fun applyPngBackground(file: ServerFileInterface, view: ImageView) {
Expand Down
Loading
Loading