diff --git a/.mapping.json b/.mapping.json index ab8295a2a..08a176731 100644 --- a/.mapping.json +++ b/.mapping.json @@ -992,7 +992,6 @@ "client/android/div-svg/src/main/java/com/yandex/div/svg/SvgCacheManager.kt":"divkit/public/client/android/div-svg/src/main/java/com/yandex/div/svg/SvgCacheManager.kt", "client/android/div-svg/src/main/java/com/yandex/div/svg/SvgDecoder.kt":"divkit/public/client/android/div-svg/src/main/java/com/yandex/div/svg/SvgDecoder.kt", "client/android/div-svg/src/main/java/com/yandex/div/svg/SvgDivImageLoader.kt":"divkit/public/client/android/div-svg/src/main/java/com/yandex/div/svg/SvgDivImageLoader.kt", - "client/android/div-svg/src/main/java/com/yandex/div/svg/SvgLoadWrapper.kt":"divkit/public/client/android/div-svg/src/main/java/com/yandex/div/svg/SvgLoadWrapper.kt", "client/android/div-tests-coverage.gradle":"divkit/public/client/android/div-tests-coverage.gradle", "client/android/div-tests.gradle":"divkit/public/client/android/div-tests.gradle", "client/android/div-video/build.gradle":"divkit/public/client/android/div-video/build.gradle", diff --git a/client/android/div-svg/src/main/java/com/yandex/div/svg/SvgDivImageLoader.kt b/client/android/div-svg/src/main/java/com/yandex/div/svg/SvgDivImageLoader.kt index 84bbbbd80..6570b5325 100644 --- a/client/android/div-svg/src/main/java/com/yandex/div/svg/SvgDivImageLoader.kt +++ b/client/android/div-svg/src/main/java/com/yandex/div/svg/SvgDivImageLoader.kt @@ -1,6 +1,8 @@ package com.yandex.div.svg +import android.content.Context import android.widget.ImageView +import com.yandex.div.core.annotations.InternalApi import com.yandex.div.core.images.DivImageDownloadCallback import com.yandex.div.core.images.DivImageLoader import com.yandex.div.core.images.LoadReference @@ -12,16 +14,17 @@ import okhttp3.Call import okhttp3.OkHttpClient import okhttp3.Request -internal class SvgDivImageLoader : DivImageLoader { +@InternalApi +public class SvgDivImageLoader(private val context: Context) : DivImageLoader { private val httpClient = OkHttpClient.Builder().build() private val coroutineScope = MainScope() private val svgDecoder = SvgDecoder() private val svgCacheManager = SvgCacheManager() - override fun hasSvgSupport() = true + override fun hasSvgSupport(): Boolean = true override fun loadImage(imageUrl: String, callback: DivImageDownloadCallback): LoadReference { - val call = createCall(imageUrl) + val call = createCallOrNull(imageUrl) val pictureDrawable = svgCacheManager.get(imageUrl) if (pictureDrawable != null) { @@ -31,9 +34,12 @@ internal class SvgDivImageLoader : DivImageLoader { coroutineScope.launch { withContext(Dispatchers.IO) { - val bytes = runCatching { - call.execute().body?.bytes() - }.getOrNull() ?: return@withContext null + val bytes = if (call == null) { + getImageData(imageUrl) + } else { + downloadImage(call) + } ?: return@withContext null + val drawable = svgDecoder.decode(bytes.inputStream()) ?: return@withContext null svgCacheManager.set(imageUrl, drawable) @@ -44,18 +50,21 @@ internal class SvgDivImageLoader : DivImageLoader { } return LoadReference { - call.cancel() + call?.cancel() } } override fun loadImage(imageUrl: String, imageView: ImageView): LoadReference { - val call = createCall(imageUrl) + val call = createCallOrNull(imageUrl) coroutineScope.launch { withContext(Dispatchers.IO) { - val bytes = runCatching { - call.execute().body?.bytes() - }.getOrNull() ?: return@withContext null + val bytes = if (call == null) { + getImageData(imageUrl) + } else { + downloadImage(call) + } ?: return@withContext null + svgDecoder.decode(bytes.inputStream()) }?.let { imageView.setImageDrawable(it) @@ -63,19 +72,34 @@ internal class SvgDivImageLoader : DivImageLoader { } return LoadReference { - call.cancel() + call?.cancel() } } override fun loadImageBytes( imageUrl: String, callback: DivImageDownloadCallback - ) = LoadReference { + ): LoadReference = LoadReference { loadImage(imageUrl, callback) } - private fun createCall(imageUrl: String) : Call { + private fun createCallOrNull(imageUrl: String) : Call? { + if (!(imageUrl.startsWith("http://") || imageUrl.startsWith("https://"))) { + return null + } val request = Request.Builder().url(imageUrl).build() return httpClient.newCall(request) } + + private fun downloadImage(call: Call): ByteArray? = runCatching { + call.execute().body?.bytes() + }.getOrNull() + + private fun getImageData(imageUrl: String): ByteArray? { + val assetPath = imageUrl.removePrefix("file:///android_asset/") + val stream = context.applicationContext?.assets?.open(assetPath) ?: return null + stream.use { + return it.readBytes() + } + } } diff --git a/client/android/div-svg/src/main/java/com/yandex/div/svg/SvgLoadWrapper.kt b/client/android/div-svg/src/main/java/com/yandex/div/svg/SvgLoadWrapper.kt deleted file mode 100644 index 9fe9edf7a..000000000 --- a/client/android/div-svg/src/main/java/com/yandex/div/svg/SvgLoadWrapper.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.yandex.div.svg - -import android.widget.ImageView -import com.yandex.div.core.annotations.InternalApi -import com.yandex.div.core.images.DivImageDownloadCallback -import com.yandex.div.core.images.DivImageLoader -import com.yandex.div.core.images.LoadReference - -/** - * A wrapper over DivImageLoader. - * Replaces chosen image loader by SVG Image Loader if image loader doesn't support svg images. - */ -@InternalApi -public class SvgLoadWrapper( - private val providedImageLoader: DivImageLoader, -) : DivImageLoader { - private val svgImageLoader: SvgDivImageLoader? = if (!providedImageLoader.hasSvgSupport()) { - SvgDivImageLoader() - } else { - null - } - - override fun loadImage(imageUrl: String, callback: DivImageDownloadCallback): LoadReference { - return getProperLoader(imageUrl).loadImage(imageUrl, callback) - } - - override fun loadImage(imageUrl: String, imageView: ImageView): LoadReference { - return getProperLoader(imageUrl).loadImage(imageUrl, imageView) - } - - override fun loadImageBytes( - imageUrl: String, - callback: DivImageDownloadCallback - ): LoadReference { - return getProperLoader(imageUrl).loadImageBytes(imageUrl, callback) - } - - private fun getProperLoader(imageUrl: String) : DivImageLoader { - return if (svgImageLoader != null && isSvg(imageUrl)) { - svgImageLoader - } else { - providedImageLoader - } - } - - private fun isSvg(imageUrl: String): Boolean { - val queryStartIndex = imageUrl.indexOf('?') - val pathEndIndex = if (queryStartIndex == -1) imageUrl.length else queryStartIndex - return imageUrl.substring(0, pathEndIndex).endsWith(".svg") - } -} diff --git a/client/android/div/src/main/java/com/yandex/div/core/DivConfiguration.java b/client/android/div/src/main/java/com/yandex/div/core/DivConfiguration.java index d4c2c0ede..39a77ae51 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/DivConfiguration.java +++ b/client/android/div/src/main/java/com/yandex/div/core/DivConfiguration.java @@ -2,16 +2,17 @@ package com.yandex.div.core; import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.yandex.div.core.annotations.InternalApi; import com.yandex.div.core.annotations.PublicApi; import com.yandex.div.core.dagger.ExperimentFlag; +import com.yandex.div.core.dagger.Names; import com.yandex.div.core.downloader.DivDownloader; import com.yandex.div.core.experiments.Experiment; import com.yandex.div.core.expression.variables.DivVariableController; import com.yandex.div.core.expression.variables.GlobalVariableController; import com.yandex.div.core.extension.DivExtensionHandler; import com.yandex.div.core.font.DivTypefaceProvider; -import com.yandex.div.core.image.DivImageLoaderWrapper; import com.yandex.div.core.images.DivImageLoader; import com.yandex.div.core.player.DivPlayerFactory; import com.yandex.div.core.player.DivPlayerPreloader; @@ -29,6 +30,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.inject.Named; + /** * Holds {@link com.yandex.div.core.view2.Div2View} configuration. * Create instance using {@link Builder} class. @@ -182,6 +185,7 @@ public class DivConfiguration { @Provides @NonNull + @Named(Names.UNWRAPPED_IMAGE_LOADER) public DivImageLoader getImageLoader() { return mImageLoader; } @@ -711,7 +715,7 @@ public class DivConfiguration { DivPlayerPreloader divPlayerPreloader = mDivPlayerPreloader == null ? divPlayerFactory.makePreloader() : mDivPlayerPreloader; return new DivConfiguration( - new DivImageLoaderWrapper(mImageLoader), + mImageLoader, mActionHandler == null ? new DivActionHandler() : mActionHandler, mDiv2Logger == null ? Div2Logger.STUB : mDiv2Logger, mDivDataChangeListener == null ? DivDataChangeListener.STUB : mDivDataChangeListener, diff --git a/client/android/div/src/main/java/com/yandex/div/core/dagger/Div2Module.java b/client/android/div/src/main/java/com/yandex/div/core/dagger/Div2Module.java index 511fd1ecb..785536ff5 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/dagger/Div2Module.java +++ b/client/android/div/src/main/java/com/yandex/div/core/dagger/Div2Module.java @@ -4,14 +4,18 @@ import android.content.Context; import android.os.Build; import android.renderscript.RenderScript; import android.view.ContextThemeWrapper; + import androidx.annotation.NonNull; import androidx.annotation.StyleRes; + import com.yandex.div.core.DivCustomContainerViewAdapter; import com.yandex.div.core.DivPreloader; import com.yandex.div.core.DivViewDataPreloader; import com.yandex.div.core.experiments.Experiment; import com.yandex.div.core.extension.DivExtensionController; import com.yandex.div.core.font.DivTypefaceProvider; +import com.yandex.div.core.image.DivImageLoaderWrapper; +import com.yandex.div.core.images.DivImageLoader; import com.yandex.div.core.player.DivPlayerPreloader; import com.yandex.div.core.resources.ContextThemeWrapperWithResourceCache; import com.yandex.div.core.view2.DivImagePreloader; @@ -91,6 +95,16 @@ abstract public class Div2Module { RenderScript.CREATE_FLAG_NONE, context.getApplicationInfo().targetSdkVersion); } + @Provides + @DivScope + @NonNull + public static DivImageLoader provideDivImageLoader( + @NonNull @Named(Names.UNWRAPPED_IMAGE_LOADER) DivImageLoader divImageLoader, + @NonNull @Named(Names.CONTEXT) Context context + ) { + return new DivImageLoaderWrapper(divImageLoader, context); + } + @Provides @DivScope @NonNull diff --git a/client/android/div/src/main/java/com/yandex/div/core/dagger/Names.kt b/client/android/div/src/main/java/com/yandex/div/core/dagger/Names.kt index 88f618caf..8609644d6 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/dagger/Names.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/dagger/Names.kt @@ -7,4 +7,5 @@ internal object Names { const val THEMED_CONTEXT = "themed_context" const val THEME = "theme" const val HAS_DEFAULTS = "has_defaults" + const val UNWRAPPED_IMAGE_LOADER = "unwrapped_image_loader" } diff --git a/client/android/div/src/main/java/com/yandex/div/core/image/DivImageLoaderWrapper.kt b/client/android/div/src/main/java/com/yandex/div/core/image/DivImageLoaderWrapper.kt index d562fc7a6..2698e5921 100644 --- a/client/android/div/src/main/java/com/yandex/div/core/image/DivImageLoaderWrapper.kt +++ b/client/android/div/src/main/java/com/yandex/div/core/image/DivImageLoaderWrapper.kt @@ -1,35 +1,50 @@ package com.yandex.div.core.image +import android.content.Context import android.widget.ImageView +import com.yandex.div.core.annotations.InternalApi +import com.yandex.div.core.dagger.DivScope import com.yandex.div.core.images.DivImageDownloadCallback import com.yandex.div.core.images.DivImageLoader import com.yandex.div.core.images.LoadReference -import com.yandex.div.svg.SvgLoadWrapper +import com.yandex.div.internal.util.makeIf +import com.yandex.div.svg.SvgDivImageLoader +import javax.inject.Inject /** - * Wraps implementation of DivImageLoader from DivConfiguration. - * - * Modifies image_url from layout before providing it to wrapped DivImageLoader. + * A wrapper over DivImageLoader. + * Applies modifiers to image URL and + * replaces chosen image loader by SVG Image Loader if image loader doesn't support svg images. */ -internal class DivImageLoaderWrapper( - providedImageLoader: DivImageLoader, -): DivImageLoader { - private val imageLoader = SvgLoadWrapper(providedImageLoader) - +@DivScope +@InternalApi +class DivImageLoaderWrapper @Inject constructor( + private val providedImageLoader: DivImageLoader, + divContext: Context, +) : DivImageLoader { private val modifiers: List = listOf( DivImageAssetUrlModifier(), ) + private val svgImageLoader: SvgDivImageLoader? = + makeIf(!providedImageLoader.hasSvgSupport()) { SvgDivImageLoader(divContext) } + override fun loadImage(imageUrl: String, callback: DivImageDownloadCallback): LoadReference { - return imageLoader.loadImage(getModifiedUrl(imageUrl), callback) + val modifiedUrl = getModifiedUrl(imageUrl) + return getProperLoader(modifiedUrl).loadImage(modifiedUrl, callback) } override fun loadImage(imageUrl: String, imageView: ImageView): LoadReference { - return imageLoader.loadImage(getModifiedUrl(imageUrl), imageView) + val modifiedUrl = getModifiedUrl(imageUrl) + return getProperLoader(modifiedUrl).loadImage(modifiedUrl, imageView) } - override fun loadImageBytes(imageUrl: String, callback: DivImageDownloadCallback): LoadReference { - return imageLoader.loadImageBytes(getModifiedUrl(imageUrl), callback) + override fun loadImageBytes( + imageUrl: String, + callback: DivImageDownloadCallback + ): LoadReference { + val modifiedUrl = getModifiedUrl(imageUrl) + return getProperLoader(modifiedUrl).loadImageBytes(modifiedUrl, callback) } private fun getModifiedUrl(initialUrl: String): String { @@ -39,4 +54,18 @@ internal class DivImageLoaderWrapper( } return url } + + private fun getProperLoader(imageUrl: String) : DivImageLoader { + return if (svgImageLoader != null && isSvg(imageUrl)) { + svgImageLoader + } else { + providedImageLoader + } + } + + private fun isSvg(imageUrl: String): Boolean { + val queryStartIndex = imageUrl.indexOf('?') + val pathEndIndex = if (queryStartIndex < 0) imageUrl.length else queryStartIndex + return imageUrl.substring(0, pathEndIndex).endsWith(".svg") + } } diff --git a/client/android/div/src/main/java/com/yandex/div/internal/util/Utils.kt b/client/android/div/src/main/java/com/yandex/div/internal/util/Utils.kt index 854072db6..3fd811a1a 100644 --- a/client/android/div/src/main/java/com/yandex/div/internal/util/Utils.kt +++ b/client/android/div/src/main/java/com/yandex/div/internal/util/Utils.kt @@ -62,3 +62,10 @@ internal class PositiveNumberDelegate( this.value = if (value.toDouble() <= 0) fallbackValue else value } } + +/** + * Like [takeIf], but creates object only if needed. Doesn't work with smart casts. + */ +inline fun makeIf(shouldMake: Boolean, constructor: () -> T?): T? { + return if (shouldMake) constructor() else null +} diff --git a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DivUtils.kt b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DivUtils.kt index 620f2afa6..893f63416 100644 --- a/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DivUtils.kt +++ b/client/android/divkit-demo-app/src/main/java/com/yandex/divkit/demo/div/DivUtils.kt @@ -31,7 +31,6 @@ import com.yandex.div.shine.DivShineExtensionHandler import com.yandex.div.shine.DivShineLogger import com.yandex.div.sizeprovider.DivSizeProviderExtensionHandler import com.yandex.div.video.ExoDivPlayerFactory -import com.yandex.div.video.ExoPlayerVideoPreloader import com.yandex.div2.DivAction import com.yandex.div2.DivData import com.yandex.div2.DivPatch diff --git a/client/android/sample/src/main/java/com/yandex/divkit/sample/MainPageActivity.kt b/client/android/sample/src/main/java/com/yandex/divkit/sample/MainPageActivity.kt index 20eebf037..59cc9e0a0 100644 --- a/client/android/sample/src/main/java/com/yandex/divkit/sample/MainPageActivity.kt +++ b/client/android/sample/src/main/java/com/yandex/divkit/sample/MainPageActivity.kt @@ -8,9 +8,9 @@ import com.yandex.div.markdown.DivMarkdownExtensionHandler import com.yandex.div.picasso.PicassoDivImageLoader import com.yandex.div.rive.OkHttpDivRiveNetworkDelegate import com.yandex.div.rive.RiveCustomViewAdapter -import com.yandex.divkit.sample.databinding.ActivityMainPageBinding import com.yandex.div.zoom.DivPinchToZoomConfiguration import com.yandex.div.zoom.DivPinchToZoomExtensionHandler +import com.yandex.divkit.sample.databinding.ActivityMainPageBinding import okhttp3.OkHttpClient class MainPageActivity : AppCompatActivity() {