diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 7ed9da937..94e151fdf 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -124,6 +124,7 @@ dependencies { compileOnly(libs.okhttp) ksp(libs.glide.compiler) + implementation(libs.zjupure.webpdecoder) implementation(libs.bundles.room) ksp(libs.androidx.room.compiler) diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SettingsActivity.kt index bce00c216..ead9601c3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/SettingsActivity.kt @@ -66,6 +66,7 @@ class SettingsActivity : SimpleActivity() { setupOpenVideosOnSeparateScreen() setupMaxBrightness() setupCropThumbnails() + setupAnimateGifs() setupDarkBackground() setupScrollHorizontally() setupScreenRotation() @@ -310,6 +311,14 @@ class SettingsActivity : SimpleActivity() { } } + private fun setupAnimateGifs() { + binding.settingsAnimateGifs.isChecked = config.animateGifs + binding.settingsAnimateGifsHolder.setOnClickListener { + binding.settingsAnimateGifs.toggle() + config.animateGifs = binding.settingsAnimateGifs.isChecked + } + } + private fun setupDarkBackground() { binding.settingsBlackBackground.isChecked = config.blackBackground binding.settingsBlackBackgroundHolder.setOnClickListener { diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/WidgetConfigureActivity.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/WidgetConfigureActivity.kt index 26c093814..7e93849f3 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/WidgetConfigureActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/activities/WidgetConfigureActivity.kt @@ -178,7 +178,7 @@ class WidgetConfigureActivity : SimpleActivity() { if (path != null) { runOnUiThread { val signature = ObjectKey(System.currentTimeMillis().toString()) - loadJpg(path, binding.configImage, config.cropThumbnails, ROUNDED_CORNERS_NONE, signature) + loadImageBase(path, binding.configImage, config.cropThumbnails, ROUNDED_CORNERS_NONE, signature) } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeFileThumbnailStyleDialog.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeFileThumbnailStyleDialog.kt index 2070f406d..0429f05e1 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeFileThumbnailStyleDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/dialogs/ChangeFileThumbnailStyleDialog.kt @@ -17,13 +17,11 @@ class ChangeFileThumbnailStyleDialog(val activity: BaseSimpleActivity) : DialogI init { binding = DialogChangeFileThumbnailStyleBinding.inflate(activity.layoutInflater).apply { dialogFileStyleRoundedCorners.isChecked = config.fileRoundedCorners - dialogFileStyleAnimateGifs.isChecked = config.animateGifs dialogFileStyleShowThumbnailVideoDuration.isChecked = config.showThumbnailVideoDuration dialogFileStyleShowThumbnailFileTypes.isChecked = config.showThumbnailFileTypes dialogFileStyleMarkFavoriteItems.isChecked = config.markFavoriteItems dialogFileStyleRoundedCornersHolder.setOnClickListener { dialogFileStyleRoundedCorners.toggle() } - dialogFileStyleAnimateGifsHolder.setOnClickListener { dialogFileStyleAnimateGifs.toggle() } dialogFileStyleShowThumbnailVideoDurationHolder.setOnClickListener { dialogFileStyleShowThumbnailVideoDuration.toggle() } dialogFileStyleShowThumbnailFileTypesHolder.setOnClickListener { dialogFileStyleShowThumbnailFileTypes.toggle() } dialogFileStyleMarkFavoriteItemsHolder.setOnClickListener { dialogFileStyleMarkFavoriteItems.toggle() } @@ -59,7 +57,6 @@ class ChangeFileThumbnailStyleDialog(val activity: BaseSimpleActivity) : DialogI override fun onClick(dialog: DialogInterface, which: Int) { config.fileRoundedCorners = binding.dialogFileStyleRoundedCorners.isChecked - config.animateGifs = binding.dialogFileStyleAnimateGifs.isChecked config.showThumbnailVideoDuration = binding.dialogFileStyleShowThumbnailVideoDuration.isChecked config.showThumbnailFileTypes = binding.dialogFileStyleShowThumbnailFileTypes.isChecked config.markFavoriteItems = binding.dialogFileStyleMarkFavoriteItems.isChecked diff --git a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt index 5de9b38f5..d8232a83e 100644 --- a/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt +++ b/app/src/main/kotlin/com/simplemobiletools/gallery/pro/extensions/Context.kt @@ -6,6 +6,7 @@ import android.content.Context import android.content.Intent import android.database.Cursor import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.graphics.drawable.PictureDrawable import android.media.AudioManager import android.os.Process @@ -14,12 +15,15 @@ import android.provider.MediaStore.Images import android.widget.ImageView import com.bumptech.glide.Glide import com.bumptech.glide.Priority +import com.bumptech.glide.integration.webp.decoder.WebpDrawable +import com.bumptech.glide.integration.webp.decoder.WebpDrawableTransformation import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DecodeFormat +import com.bumptech.glide.load.MultiTransformation import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.load.resource.bitmap.BitmapTransitionOptions import com.bumptech.glide.load.resource.bitmap.CenterCrop +import com.bumptech.glide.load.resource.bitmap.FitCenter import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions import com.bumptech.glide.request.RequestListener @@ -37,7 +41,6 @@ import com.simplemobiletools.gallery.pro.interfaces.* import com.simplemobiletools.gallery.pro.models.* import com.simplemobiletools.gallery.pro.svg.SvgSoftwareLayerSetter import com.squareup.picasso.Picasso -import pl.droidsonroids.gif.GifDrawable import java.io.File import java.io.FileInputStream import java.nio.ByteBuffer @@ -463,31 +466,11 @@ fun Context.loadImage( roundCorners: Int, signature: ObjectKey, skipMemoryCacheAtPaths: ArrayList? = null ) { target.isHorizontalScrolling = horizontalScroll - if (type == TYPE_IMAGES || type == TYPE_VIDEOS || type == TYPE_RAWS || type == TYPE_PORTRAITS) { - if (type == TYPE_IMAGES && path.isPng()) { - loadPng(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths) - } else { - loadJpg(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths) - } - } else if (type == TYPE_GIFS) { - if (!animateGifs) { - loadStaticGIF(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths) - return - } - - try { - val gifDrawable = GifDrawable(path) - target.setImageDrawable(gifDrawable) - gifDrawable.start() - - target.scaleType = if (cropThumbnails) ImageView.ScaleType.CENTER_CROP else ImageView.ScaleType.FIT_CENTER - } catch (e: Exception) { - loadStaticGIF(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths) - } catch (e: OutOfMemoryError) { - loadStaticGIF(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths) - } - } else if (type == TYPE_SVGS) { + if (type == TYPE_SVGS) { loadSVG(path, target, cropThumbnails, roundCorners, signature) + } else { + val tryLoadingWithPicasso = type == TYPE_IMAGES && path.isPng() + loadImageBase(path, target, cropThumbnails, roundCorners, signature, skipMemoryCacheAtPaths, animateGifs, tryLoadingWithPicasso) } } @@ -512,109 +495,72 @@ fun Context.getPathLocation(path: String): Int { } } -fun Context.loadPng( +fun Context.loadImageBase( path: String, target: MySquareImageView, cropThumbnails: Boolean, roundCorners: Int, signature: ObjectKey, - skipMemoryCacheAtPaths: ArrayList? = null + skipMemoryCacheAtPaths: ArrayList? = null, + animate: Boolean = false, + tryLoadingWithPicasso: Boolean = false, + crossFadeDuration: Int = 300 ) { val options = RequestOptions() .signature(signature) .skipMemoryCache(skipMemoryCacheAtPaths?.contains(path) == true) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) .priority(Priority.LOW) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) .format(DecodeFormat.PREFER_ARGB_8888) - if (cropThumbnails) options.centerCrop() else options.fitCenter() + if (cropThumbnails) { + options.optionalTransform(CenterCrop()) + options.optionalTransform(WebpDrawable::class.java, WebpDrawableTransformation(CenterCrop())) + } else { + options.optionalTransform(FitCenter()) + options.optionalTransform(WebpDrawable::class.java, WebpDrawableTransformation(FitCenter())) + } + + // animation is only supported without rounded corners + if (animate && roundCorners == ROUNDED_CORNERS_NONE) { + // this is required to make glide cache aware of changes + options.decode(Drawable::class.java) + } else { + options.dontAnimate() + // don't animate is not enough for webp files, decode as bitmap forces first frame use in animated webps + options.decode(Bitmap::class.java) + } + + if (roundCorners != ROUNDED_CORNERS_NONE) { + val cornerSize = if (roundCorners == ROUNDED_CORNERS_SMALL) com.simplemobiletools.commons.R.dimen.rounded_corner_radius_small else com.simplemobiletools.commons.R.dimen.rounded_corner_radius_big + val cornerRadius = resources.getDimension(cornerSize).toInt() + val roundedCornersTransform = RoundedCorners(cornerRadius) + options.optionalTransform(MultiTransformation(CenterCrop(), roundedCornersTransform)) + options.optionalTransform(WebpDrawable::class.java, MultiTransformation(WebpDrawableTransformation(CenterCrop()), WebpDrawableTransformation(roundedCornersTransform))) + } + var builder = Glide.with(applicationContext) - .asBitmap() .load(path) .apply(options) - .listener(object : RequestListener { - override fun onLoadFailed(e: GlideException?, model: Any?, targetBitmap: Target, isFirstResource: Boolean): Boolean { + .transition(DrawableTransitionOptions.withCrossFade(crossFadeDuration)) + + if (tryLoadingWithPicasso) { + builder = builder.listener(object : RequestListener { + override fun onLoadFailed(e: GlideException?, model: Any?, targetBitmap: Target, isFirstResource: Boolean): Boolean { tryLoadingWithPicasso(path, target, cropThumbnails, roundCorners, signature) return true } override fun onResourceReady( - resource: Bitmap, + resource: Drawable, model: Any, - targetBitmap: Target, + targetBitmap: Target, dataSource: DataSource, isFirstResource: Boolean ): Boolean { return false } }) - - if (roundCorners != ROUNDED_CORNERS_NONE) { - val cornerSize = - if (roundCorners == ROUNDED_CORNERS_SMALL) com.simplemobiletools.commons.R.dimen.rounded_corner_radius_small else com.simplemobiletools.commons.R.dimen.rounded_corner_radius_big - val cornerRadius = resources.getDimension(cornerSize).toInt() - builder = builder.transform(CenterCrop(), RoundedCorners(cornerRadius)) - } - - builder.into(target) -} - -fun Context.loadJpg( - path: String, - target: MySquareImageView, - cropThumbnails: Boolean, - roundCorners: Int, - signature: ObjectKey, - skipMemoryCacheAtPaths: ArrayList? = null -) { - val options = RequestOptions() - .signature(signature) - .skipMemoryCache(skipMemoryCacheAtPaths?.contains(path) == true) - .priority(Priority.LOW) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - - if (cropThumbnails) options.centerCrop() else options.fitCenter() - var builder = Glide.with(applicationContext) - .asBitmap() - .load(path) - .apply(options) - .transition(BitmapTransitionOptions.withCrossFade()) - - if (roundCorners != ROUNDED_CORNERS_NONE) { - val cornerSize = - if (roundCorners == ROUNDED_CORNERS_SMALL) com.simplemobiletools.commons.R.dimen.rounded_corner_radius_small else com.simplemobiletools.commons.R.dimen.rounded_corner_radius_big - val cornerRadius = resources.getDimension(cornerSize).toInt() - builder = builder.transform(CenterCrop(), RoundedCorners(cornerRadius)) - } - - builder.into(target) -} - -fun Context.loadStaticGIF( - path: String, - target: MySquareImageView, - cropThumbnails: Boolean, - roundCorners: Int, - signature: ObjectKey, - skipMemoryCacheAtPaths: ArrayList? = null -) { - val options = RequestOptions() - .signature(signature) - .skipMemoryCache(skipMemoryCacheAtPaths?.contains(path) == true) - .priority(Priority.LOW) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - - if (cropThumbnails) options.centerCrop() else options.fitCenter() - var builder = Glide.with(applicationContext) - .asBitmap() // make sure the GIF wont animate - .load(path) - .apply(options) - - if (roundCorners != ROUNDED_CORNERS_NONE) { - val cornerSize = - if (roundCorners == ROUNDED_CORNERS_SMALL) com.simplemobiletools.commons.R.dimen.rounded_corner_radius_small else com.simplemobiletools.commons.R.dimen.rounded_corner_radius_big - val cornerRadius = resources.getDimension(cornerSize).toInt() - builder = builder.transform(CenterCrop(), RoundedCorners(cornerRadius)) } builder.into(target) diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index bf1afdddc..08d5f7b3d 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -326,6 +326,21 @@ + + + + + + - - - - - -