mirror of
https://github.com/divkit/divkit.git
synced 2026-05-07 20:02:32 +00:00
Add local SVG load
commit_hash:0b5c855bb8ae2edd151706034d8966cfb7ca3d10
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
+42
-13
@@ -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<DivImageUrlModifier> = 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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,3 +62,10 @@ internal class PositiveNumberDelegate<T: Number>(
|
||||
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 <T> makeIf(shouldMake: Boolean, constructor: () -> T?): T? {
|
||||
return if (shouldMake) constructor() else null
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user