diff --git a/app/android/src/main/java/com/seiko/imageloader/demo/MainActivity.kt b/app/android/src/main/java/com/seiko/imageloader/demo/MainActivity.kt index 70666f9b..b57828b5 100644 --- a/app/android/src/main/java/com/seiko/imageloader/demo/MainActivity.kt +++ b/app/android/src/main/java/com/seiko/imageloader/demo/MainActivity.kt @@ -11,6 +11,7 @@ import com.seiko.imageloader.component.setupDefaultComponents import com.seiko.imageloader.demo.util.LocalResLoader import com.seiko.imageloader.demo.util.ResLoader import com.seiko.imageloader.demo.util.commonConfig +import com.seiko.imageloader.option.androidContext import okio.Path.Companion.toOkioPath class MainActivity : ComponentActivity() { @@ -29,8 +30,11 @@ class MainActivity : ComponentActivity() { private fun generateImageLoader(): ImageLoader { return ImageLoader { commonConfig() + options { + androidContext(applicationContext) + } components { - setupDefaultComponents(applicationContext) + setupDefaultComponents() } interceptor { memoryCacheConfig { diff --git a/extension/blur/src/commonMain/kotlin/com/seiko/imageloader/intercept/BlurInterceptor.kt b/extension/blur/src/commonMain/kotlin/com/seiko/imageloader/intercept/BlurInterceptor.kt index 85bff5ae..767a0b03 100644 --- a/extension/blur/src/commonMain/kotlin/com/seiko/imageloader/intercept/BlurInterceptor.kt +++ b/extension/blur/src/commonMain/kotlin/com/seiko/imageloader/intercept/BlurInterceptor.kt @@ -1,9 +1,8 @@ package com.seiko.imageloader.intercept import com.seiko.imageloader.Bitmap -import com.seiko.imageloader.model.BlurEffects import com.seiko.imageloader.model.ImageResult -import com.seiko.imageloader.model.KEY_BLUR_RADIUS +import com.seiko.imageloader.model.blurEffects // blur only support Bitmap class BlurInterceptor : Interceptor { @@ -11,8 +10,7 @@ class BlurInterceptor : Interceptor { val request = chain.request val result = chain.proceed(request) if (result is ImageResult.Bitmap) { - val blurEffects = request.extra[KEY_BLUR_RADIUS] as? BlurEffects - ?: return result + val blurEffects = request.blurEffects ?: return result return result.copy(bitmap = blur(result.bitmap, blurEffects.radius)) } return result diff --git a/extension/blur/src/commonMain/kotlin/com/seiko/imageloader/model/ImageRequest.blur.kt b/extension/blur/src/commonMain/kotlin/com/seiko/imageloader/model/ImageRequest.blur.kt index 085dd408..75b8066d 100644 --- a/extension/blur/src/commonMain/kotlin/com/seiko/imageloader/model/ImageRequest.blur.kt +++ b/extension/blur/src/commonMain/kotlin/com/seiko/imageloader/model/ImageRequest.blur.kt @@ -6,8 +6,11 @@ fun ImageRequestBuilder.blur(radius: Int) { } } +internal val ImageRequest.blurEffects: BlurEffects? + get() = extra[KEY_BLUR_RADIUS] as? BlurEffects + internal data class BlurEffects( val radius: Int, ) -internal const val KEY_BLUR_RADIUS = "KEY_BLUR_RADIUS" +private const val KEY_BLUR_RADIUS = "KEY_BLUR_RADIUS" diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/SetupComponents.android.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/SetupComponents.android.kt index 292c01cf..11212630 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/SetupComponents.android.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/SetupComponents.android.kt @@ -16,9 +16,9 @@ import com.seiko.imageloader.component.mapper.ResourceIntMapper import com.seiko.imageloader.component.mapper.ResourceUriMapper fun ComponentRegistryBuilder.setupAndroidComponents( - context: Context, - density: Density = Density(context), - maxImageSize: Int = 4096, + context: Context? = null, + density: Density? = context?.let { Density(it) }, + maxImageSize: Int = BitmapFactoryDecoder.DEFAULT_MAX_PARALLELISM, ) { // Mappers add(ResourceUriMapper(context)) diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/BitmapFactoryDecoder.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/BitmapFactoryDecoder.kt index 43cfaef4..71b8e3b8 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/BitmapFactoryDecoder.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/BitmapFactoryDecoder.kt @@ -5,6 +5,7 @@ import android.graphics.Bitmap import android.graphics.BitmapFactory import android.os.Build.VERSION.SDK_INT import com.seiko.imageloader.option.Options +import com.seiko.imageloader.option.androidContext import com.seiko.imageloader.util.DecodeUtils import com.seiko.imageloader.util.ExifData import com.seiko.imageloader.util.ExifUtils @@ -83,7 +84,7 @@ class BitmapFactoryDecoder private constructor( /** Compute and set [BitmapFactory.Options.inPreferredConfig]. */ private fun BitmapFactory.Options.configureConfig(exifData: ExifData) { - var config = options.config.toBitmapConfig() + var config = options.imageConfig.toBitmapConfig() // Disable hardware bitmaps if we need to perform EXIF transformations. if (exifData.isFlipped || exifData.isRotated) { @@ -164,16 +165,22 @@ class BitmapFactoryDecoder private constructor( } } - class Factory constructor( - private val context: Context, - private val maxImageSize: Int, + class Factory( + private val context: Context? = null, + private val maxImageSize: Int = DEFAULT_MAX_IMAGE_SIZE, maxParallelism: Int = DEFAULT_MAX_PARALLELISM, ) : Decoder.Factory { private val parallelismLock = Semaphore(maxParallelism) override suspend fun create(source: DecodeSource, options: Options): Decoder { - return BitmapFactoryDecoder(context, source, options, maxImageSize, parallelismLock) + return BitmapFactoryDecoder( + context = context ?: options.androidContext, + source = source, + options = options, + maxImageSize = maxImageSize, + parallelismLock = parallelismLock, + ) } } @@ -195,5 +202,6 @@ class BitmapFactoryDecoder private constructor( internal companion object { internal const val DEFAULT_MAX_PARALLELISM = 4 + internal const val DEFAULT_MAX_IMAGE_SIZE = 4096 } } diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt index e3a680a2..5902c5b7 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/GifDecoder.kt @@ -40,7 +40,7 @@ class GifDecoder private constructor( val movie: Movie? = bufferedSource.use { Movie.decodeStream(it.inputStream()) } check(movie != null && movie.width() > 0 && movie.height() > 0) { "Failed to decode GIF." } - val config = options.config.toBitmapConfig() + val config = options.imageConfig.toBitmapConfig() val movieConfig = when { // movie.isOpaque && options.allowRgb565 -> Bitmap.Config.RGB_565 config.isHardware -> Bitmap.Config.ARGB_8888 diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/ImageDecoderDecoder.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/ImageDecoderDecoder.kt index e13e8f77..53adbe5c 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/ImageDecoderDecoder.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/ImageDecoderDecoder.kt @@ -12,6 +12,7 @@ import com.seiko.imageloader.component.fetcher.ContentUriFetcher import com.seiko.imageloader.component.fetcher.ResourceUriFetcher import com.seiko.imageloader.model.metadata import com.seiko.imageloader.option.Options +import com.seiko.imageloader.option.androidContext import com.seiko.imageloader.toImage import com.seiko.imageloader.util.FrameDelayRewritingSource import com.seiko.imageloader.util.ScaleDrawable @@ -103,7 +104,7 @@ class ImageDecoderDecoder private constructor( } private fun ImageDecoder.configureImageDecoderProperties() { - val config = options.config.toBitmapConfig() + val config = options.imageConfig.toBitmapConfig() allocator = if (config.isHardware) { ImageDecoder.ALLOCATOR_HARDWARE } else { @@ -143,15 +144,20 @@ class ImageDecoderDecoder private constructor( return ScaleDrawable(baseDrawable, options.scale) } - class Factory @JvmOverloads constructor( - private val context: Context, + class Factory( + private val context: Context? = null, private val enforceMinimumFrameDelay: Boolean = true, ) : Decoder.Factory { override suspend fun create(source: DecodeSource, options: Options): Decoder? { if (!options.playAnimate) return null if (!isApplicable(source.source)) return null - return ImageDecoderDecoder(context, source, options, enforceMinimumFrameDelay) + return ImageDecoderDecoder( + context = context ?: options.androidContext, + source = source, + options = options, + enforceMinimumFrameDelay = enforceMinimumFrameDelay, + ) } private fun isApplicable(source: BufferedSource): Boolean { diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/SvgDecoder.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/SvgDecoder.kt index 099d0822..9a6584ad 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/SvgDecoder.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/decoder/SvgDecoder.kt @@ -4,6 +4,7 @@ import androidx.compose.ui.unit.Density import com.caverock.androidsvg.SVG import com.seiko.imageloader.model.mimeType import com.seiko.imageloader.option.Options +import com.seiko.imageloader.option.androidContext import com.seiko.imageloader.util.SVGPainter import com.seiko.imageloader.util.isSvg @@ -27,15 +28,15 @@ class SvgDecoder private constructor( ) } - class Factory constructor( - private val density: Density, + class Factory( + private val density: Density? = null, ) : Decoder.Factory { override suspend fun create(source: DecodeSource, options: Options): Decoder? { if (!isApplicable(source)) return null return SvgDecoder( source = source, - density = density, + density = density ?: Density(options.androidContext), options = options, ) } diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/AssetUriFetcher.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/AssetUriFetcher.kt index 3630eb86..2eeea47e 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/AssetUriFetcher.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/AssetUriFetcher.kt @@ -7,6 +7,7 @@ import com.seiko.imageloader.model.extraData import com.seiko.imageloader.model.metadata import com.seiko.imageloader.model.mimeType import com.seiko.imageloader.option.Options +import com.seiko.imageloader.option.androidContext import com.seiko.imageloader.util.getMimeTypeFromUrl import com.seiko.imageloader.util.isAssetUri import okio.buffer @@ -28,11 +29,16 @@ class AssetUriFetcher private constructor( ) } - class Factory(private val context: Context) : Fetcher.Factory { + class Factory( + private val context: Context? = null, + ) : Fetcher.Factory { override fun create(data: Any, options: Options): Fetcher? { if (data !is Uri) return null if (!isAssetUri(data)) return null - return AssetUriFetcher(context, data) + return AssetUriFetcher( + context = context ?: options.androidContext, + data = data, + ) } } diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/ContentUriFetcher.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/ContentUriFetcher.kt index b9b387a6..96c02117 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/ContentUriFetcher.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/ContentUriFetcher.kt @@ -15,6 +15,7 @@ import com.seiko.imageloader.model.extraData import com.seiko.imageloader.model.metadata import com.seiko.imageloader.model.mimeType import com.seiko.imageloader.option.Options +import com.seiko.imageloader.option.androidContext import okio.buffer import okio.source import android.net.Uri as AndroidUri @@ -78,13 +79,16 @@ class ContentUriFetcher private constructor( } class Factory( - private val context: Context, + private val context: Context? = null, ) : Fetcher.Factory { override fun create(data: Any, options: Options): Fetcher? { if (data !is Uri) return null if (!isApplicable(data)) return null - return ContentUriFetcher(context, data) + return ContentUriFetcher( + context = context ?: options.androidContext, + data = data, + ) } private fun isApplicable(data: Uri) = data.scheme == SCHEME_CONTENT diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/ResourceUriFetcher.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/ResourceUriFetcher.kt index 084ba1b3..5ca9d0d0 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/ResourceUriFetcher.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/fetcher/ResourceUriFetcher.kt @@ -21,6 +21,7 @@ import com.seiko.imageloader.model.extraData import com.seiko.imageloader.model.metadata import com.seiko.imageloader.model.mimeType import com.seiko.imageloader.option.Options +import com.seiko.imageloader.option.androidContext import com.seiko.imageloader.toImage import com.seiko.imageloader.util.DrawableUtils import com.seiko.imageloader.util.getMimeTypeFromUrl @@ -64,7 +65,7 @@ class ResourceUriFetcher private constructor( FetchResult.Bitmap( bitmap = DrawableUtils.convertToBitmap( drawable = drawable, - config = options.config.toBitmapConfig(), + config = options.imageConfig.toBitmapConfig(), scale = options.scale, allowInexactSize = options.allowInexactSize, ), @@ -98,11 +99,17 @@ class ResourceUriFetcher private constructor( throw IllegalStateException("Invalid $SCHEME_ANDROID_RESOURCE URI: $data") } - class Factory(private val context: Context) : Fetcher.Factory { + class Factory( + private val context: Context? = null, + ) : Fetcher.Factory { override fun create(data: Any, options: Options): Fetcher? { if (data !is Uri) return null if (!isApplicable(data)) return null - return ResourceUriFetcher(context, data, options) + return ResourceUriFetcher( + context = context ?: options.androidContext, + data = data, + options = options, + ) } private fun isApplicable(data: Uri): Boolean { @@ -162,6 +169,7 @@ private fun Context.getXmlDrawableCompat(resources: Resources, @XmlRes resId: In theme, ) } + "animated-vector" -> { return AnimatedVectorDrawableCompat.createFromXmlInner( this, diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/keyer/UriKeyer.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/keyer/UriKeyer.kt index 78fbc15a..5234fe7a 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/keyer/UriKeyer.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/keyer/UriKeyer.kt @@ -5,8 +5,9 @@ import android.content.Context import android.content.res.Configuration import com.eygraber.uri.Uri import com.seiko.imageloader.option.Options +import com.seiko.imageloader.option.androidContext -class UriKeyer(private val context: Context) : Keyer { +class UriKeyer(private val context: Context? = null) : Keyer { override fun key(data: Any, options: Options, type: Keyer.Type): String? { if (data !is Uri) return null @@ -15,11 +16,12 @@ class UriKeyer(private val context: Context) : Keyer { // android uri is mapper to android.resource://example.package.name/12345678, // but resId is changeable, here is convert resId to entryName. val resId = data.pathSegments.lastOrNull()?.toIntOrNull() ?: return data.toString() - val entryName = context.resources.getResourceEntryName(resId) + val androidContext = context ?: options.androidContext + val entryName = androidContext.resources.getResourceEntryName(resId) val newUri = data.buildUpon().path(entryName).build() // 'android.resource' uris can change if night mode is enabled/disabled. - return "$newUri-${context.resources.configuration.nightMode}" + return "$newUri-${androidContext.resources.configuration.nightMode}" } private val Configuration.nightMode: Int diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/mapper/ResourceIntMapper.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/mapper/ResourceIntMapper.kt index 4452ddab..0286e104 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/mapper/ResourceIntMapper.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/mapper/ResourceIntMapper.kt @@ -6,13 +6,15 @@ import android.content.res.Resources import androidx.annotation.DrawableRes import com.eygraber.uri.Uri import com.seiko.imageloader.option.Options +import com.seiko.imageloader.option.androidContext -class ResourceIntMapper(private val context: Context) : Mapper { +class ResourceIntMapper(private val context: Context? = null) : Mapper { override fun map(data: Any, options: Options): Uri? { if (data !is Int) return null - if (!isApplicable(data, context)) return null - return Uri.parse("$SCHEME_ANDROID_RESOURCE://${context.packageName}/$data") + val androidContext = context ?: options.androidContext + if (!isApplicable(data, androidContext)) return null + return Uri.parse("$SCHEME_ANDROID_RESOURCE://${androidContext.packageName}/$data") } private fun isApplicable(@DrawableRes data: Int, context: Context): Boolean { diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/mapper/ResourceUriMapper.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/mapper/ResourceUriMapper.kt index e3b85a4f..1a12b8fc 100644 --- a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/mapper/ResourceUriMapper.kt +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/component/mapper/ResourceUriMapper.kt @@ -4,20 +4,24 @@ import android.content.ContentResolver.SCHEME_ANDROID_RESOURCE import android.content.Context import com.eygraber.uri.Uri import com.seiko.imageloader.option.Options +import com.seiko.imageloader.option.androidContext /** * Maps android.resource uris with resource names to uris containing their resources ID. i.e.: * * android.resource://example.package.name/drawable/image -> android.resource://example.package.name/12345678 */ -class ResourceUriMapper(private val context: Context) : Mapper { +class ResourceUriMapper( + private val context: Context? = null, +) : Mapper { override fun map(data: Any, options: Options): Uri? { if (data !is Uri) return null if (!isApplicable(data)) return null val packageName = data.authority.orEmpty() - val resources = context.packageManager.getResourcesForApplication(packageName) + val androidContext = context ?: options.androidContext + val resources = androidContext.packageManager.getResourcesForApplication(packageName) val (type, name) = data.pathSegments val id = resources.getIdentifier(name, type, packageName) check(id != 0) { "Invalid $SCHEME_ANDROID_RESOURCE URI: $data" } diff --git a/image-loader/src/androidMain/kotlin/com/seiko/imageloader/option/Options.android.kt b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/option/Options.android.kt new file mode 100644 index 00000000..62e81084 --- /dev/null +++ b/image-loader/src/androidMain/kotlin/com/seiko/imageloader/option/Options.android.kt @@ -0,0 +1,16 @@ +package com.seiko.imageloader.option + +import android.content.Context + +fun OptionsBuilder.androidContext(context: Context) { + extra { + set(KEY_ANDROID_CONTEXT, context) + } +} + +val Options.androidContext: Context + get() = requireNotNull(extra[KEY_ANDROID_CONTEXT] as? Context) { + "not androidContext in options extra" + } + +private const val KEY_ANDROID_CONTEXT = "KEY_ANDROID_CONTEXT" diff --git a/image-loader/src/androidMain/singleton/com/seiko/imageloader/component/SetupDefaultComponents.android.kt b/image-loader/src/androidMain/singleton/com/seiko/imageloader/component/SetupDefaultComponents.android.kt index 48201df3..49523c45 100644 --- a/image-loader/src/androidMain/singleton/com/seiko/imageloader/component/SetupDefaultComponents.android.kt +++ b/image-loader/src/androidMain/singleton/com/seiko/imageloader/component/SetupDefaultComponents.android.kt @@ -2,13 +2,14 @@ package com.seiko.imageloader.component import android.content.Context import androidx.compose.ui.unit.Density +import com.seiko.imageloader.component.decoder.BitmapFactoryDecoder import com.seiko.imageloader.util.httpEngine import io.ktor.client.HttpClient fun ComponentRegistryBuilder.setupDefaultComponents( - context: Context, - density: Density = Density(context), - maxImageSize: Int = 4096, + context: Context? = null, + density: Density? = context?.let { Density(it) }, + maxImageSize: Int = BitmapFactoryDecoder.DEFAULT_MAX_PARALLELISM, httpClient: () -> HttpClient = { HttpClient(httpEngine) }, ) { setupKtorComponents(httpClient) diff --git a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/ImageLoaderConfig.kt b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/ImageLoaderConfig.kt index 6d8d3a91..0439e396 100644 --- a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/ImageLoaderConfig.kt +++ b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/ImageLoaderConfig.kt @@ -3,6 +3,7 @@ package com.seiko.imageloader import com.seiko.imageloader.component.ComponentRegistryBuilder import com.seiko.imageloader.intercept.InterceptorsBuilder import com.seiko.imageloader.option.Options +import com.seiko.imageloader.option.OptionsBuilder import com.seiko.imageloader.util.Logger import com.seiko.imageloader.util.defaultImageScope import kotlinx.coroutines.CoroutineScope @@ -25,10 +26,10 @@ class ImageLoaderConfigBuilder internal constructor() { } var logger = Logger.None - var options = Options() private val interceptorsBuilder = InterceptorsBuilder() private val componentsBuilder = ComponentRegistryBuilder() + private val optionsBuilder = OptionsBuilder() fun interceptor(builder: InterceptorsBuilder.() -> Unit) { interceptorsBuilder.run(builder) @@ -38,10 +39,14 @@ class ImageLoaderConfigBuilder internal constructor() { componentsBuilder.run(builder) } + fun options(builder: OptionsBuilder.() -> Unit) { + optionsBuilder.run(builder) + } + internal fun build() = ImageLoaderConfig( imageScope = imageScope, logger = logger, - defaultOptions = options, + defaultOptions = optionsBuilder.build(), engine = ImageLoaderEngine( interceptors = interceptorsBuilder.build(), componentRegistry = componentsBuilder.build(), diff --git a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/cache/disk/DiskCacheBuilder.kt b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/cache/disk/DiskCacheBuilder.kt index 7f35bfc8..b0a138ac 100644 --- a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/cache/disk/DiskCacheBuilder.kt +++ b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/cache/disk/DiskCacheBuilder.kt @@ -51,7 +51,7 @@ class DiskCacheBuilder internal constructor(private val fileSystem: FileSystem) this.cleanupDispatcher = dispatcher } - fun build(): DiskCache { + internal fun build(): DiskCache { val directory = checkNotNull(directory) { "directory == null" } val maxSize = if (maxSizePercent > 0) { try { diff --git a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/cache/memory/MemoryCacheBuilder.kt b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/cache/memory/MemoryCacheBuilder.kt index 5800634e..4159e7bb 100644 --- a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/cache/memory/MemoryCacheBuilder.kt +++ b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/cache/memory/MemoryCacheBuilder.kt @@ -19,7 +19,7 @@ class MemoryCacheBuilder internal constructor() { weakReferencesEnabled = enable } - fun build(): MemoryCache { + internal fun build(): MemoryCache { val weakMemoryCache = if (weakReferencesEnabled) { RealWeakMemoryCache() } else { diff --git a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/component/ComponentRegistryBuilder.kt b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/component/ComponentRegistryBuilder.kt index 01af1ada..47b1316e 100644 --- a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/component/ComponentRegistryBuilder.kt +++ b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/component/ComponentRegistryBuilder.kt @@ -28,7 +28,7 @@ class ComponentRegistryBuilder( decoderFactories.add(decoderFactory) } - fun build(): ComponentRegistry = ComponentRegistry( + internal fun build(): ComponentRegistry = ComponentRegistry( mappers = mappers, keyers = keyers, fetcherFactories = fetcherFactories, diff --git a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/intercept/Interceptor.kt b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/intercept/Interceptor.kt index 928d678f..a998bcfe 100644 --- a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/intercept/Interceptor.kt +++ b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/intercept/Interceptor.kt @@ -17,10 +17,9 @@ interface Interceptor { val request: ImageRequest val options: Options get() = - // copy options, otherwise default options will be change - config.defaultOptions.copy().also { + config.defaultOptions.newBuilder { request.optionsBuilders.forEach { builder -> - it.run(builder) + builder.invoke(this) } } diff --git a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/intercept/InterceptorsBuilder.kt b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/intercept/InterceptorsBuilder.kt index ea1630ca..ecdcb68a 100644 --- a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/intercept/InterceptorsBuilder.kt +++ b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/intercept/InterceptorsBuilder.kt @@ -44,7 +44,7 @@ class InterceptorsBuilder { diskCache = block } - fun build(): List { + internal fun build(): List { return interceptors + if (useDefaultInterceptors) { listOfNotNull( MappedInterceptor(), diff --git a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/model/ImageRequest.kt b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/model/ImageRequest.kt index b97d4d51..78e87dd6 100644 --- a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/model/ImageRequest.kt +++ b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/model/ImageRequest.kt @@ -6,14 +6,14 @@ import androidx.compose.ui.graphics.painter.Painter import com.seiko.imageloader.component.ComponentRegistry import com.seiko.imageloader.component.ComponentRegistryBuilder import com.seiko.imageloader.intercept.Interceptor -import com.seiko.imageloader.option.Options +import com.seiko.imageloader.option.OptionsBuilder import com.seiko.imageloader.option.Scale import com.seiko.imageloader.option.SizeResolver @Immutable class ImageRequest internal constructor( val data: Any, - val optionsBuilders: List Unit>, + val optionsBuilders: List Unit>, val extra: ExtraData, internal val components: ComponentRegistry?, internal val interceptors: List?, @@ -33,7 +33,7 @@ class ImageRequestBuilder { private var data: Any? private var extraData: ExtraData? - private val optionsBuilders: MutableList Unit> + private val optionsBuilders: MutableList Unit> private var componentBuilder: ComponentRegistryBuilder? private var interceptors: MutableList? private var eventListener: MutableList? @@ -78,7 +78,7 @@ class ImageRequestBuilder { } } - fun options(block: Options.() -> Unit) { + fun options(block: OptionsBuilder.() -> Unit) { optionsBuilders.add(block) } @@ -110,7 +110,7 @@ class ImageRequestBuilder { errorPainter = loader } - fun build() = ImageRequest( + internal fun build() = ImageRequest( data = data ?: NullRequestData, optionsBuilders = optionsBuilders, components = componentBuilder?.build(), diff --git a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/option/Options.kt b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/option/Options.kt index 3b678318..e3c00faf 100644 --- a/image-loader/src/commonMain/kotlin/com/seiko/imageloader/option/Options.kt +++ b/image-loader/src/commonMain/kotlin/com/seiko/imageloader/option/Options.kt @@ -1,25 +1,26 @@ package com.seiko.imageloader.option import com.seiko.imageloader.cache.CachePolicy +import com.seiko.imageloader.model.EmptyExtraData +import com.seiko.imageloader.model.ExtraData +import com.seiko.imageloader.model.ExtraDataBuilder +import com.seiko.imageloader.model.extraData -data class Options( - var allowInexactSize: Boolean = false, - var premultipliedAlpha: Boolean = true, - var retryIfDiskDecodeError: Boolean = true, - var config: ImageConfig = ImageConfig.ARGB_8888, - var scale: Scale = Scale.AUTO, - var sizeResolver: SizeResolver = SizeResolver.Unspecified, - var memoryCachePolicy: CachePolicy = CachePolicy.ENABLED, - var diskCachePolicy: CachePolicy = CachePolicy.ENABLED, - /** - * play gif or not - */ - var playAnimate: Boolean = true, +data class Options internal constructor( + val allowInexactSize: Boolean, + val premultipliedAlpha: Boolean, + val retryIfDiskDecodeError: Boolean, + val imageConfig: ImageConfig, + val scale: Scale, + val sizeResolver: SizeResolver, + val memoryCachePolicy: CachePolicy, + val diskCachePolicy: CachePolicy, + val playAnimate: Boolean, + val repeatCount: Int, + val extra: ExtraData, ) { - var repeatCount: Int = REPEAT_INFINITE - set(value) { - field = maxOf(value, REPEAT_INFINITE) - } + fun newBuilder(block: OptionsBuilder.() -> Unit) = + OptionsBuilder(this).apply(block).build() enum class ImageConfig { ALPHA_8, @@ -35,3 +36,77 @@ data class Options( internal const val REPEAT_INFINITE = -1 } } + +class OptionsBuilder { + + var allowInexactSize: Boolean + var premultipliedAlpha: Boolean + var retryIfDiskDecodeError: Boolean + var imageConfig: Options.ImageConfig + var scale: Scale + var sizeResolver: SizeResolver + var memoryCachePolicy: CachePolicy + var diskCachePolicy: CachePolicy + var playAnimate: Boolean + private var _repeatCount: Int + private var extraData: ExtraData? + + internal constructor() { + allowInexactSize = false + premultipliedAlpha = true + retryIfDiskDecodeError = true + imageConfig = Options.ImageConfig.ARGB_8888 + scale = Scale.AUTO + sizeResolver = SizeResolver.Unspecified + memoryCachePolicy = CachePolicy.ENABLED + diskCachePolicy = CachePolicy.ENABLED + playAnimate = true + _repeatCount = Options.REPEAT_INFINITE + extraData = null + } + + internal constructor(options: Options) { + allowInexactSize = options.allowInexactSize + premultipliedAlpha = options.premultipliedAlpha + retryIfDiskDecodeError = options.retryIfDiskDecodeError + imageConfig = options.imageConfig + scale = options.scale + sizeResolver = options.sizeResolver + memoryCachePolicy = options.memoryCachePolicy + diskCachePolicy = options.diskCachePolicy + playAnimate = options.playAnimate + _repeatCount = options.repeatCount + extraData = options.extra + } + + var repeatCount: Int + get() = _repeatCount + set(value) { + _repeatCount = maxOf(value, Options.REPEAT_INFINITE) + } + + fun extra(builder: ExtraDataBuilder.() -> Unit) { + extraData = extraData + ?.takeUnless { it.isEmpty() } + ?.toMutableMap() + ?.apply(builder) + ?: extraData(builder) + } + + internal fun build() = Options( + allowInexactSize = allowInexactSize, + premultipliedAlpha = premultipliedAlpha, + retryIfDiskDecodeError = retryIfDiskDecodeError, + imageConfig = imageConfig, + scale = scale, + sizeResolver = sizeResolver, + memoryCachePolicy = memoryCachePolicy, + diskCachePolicy = diskCachePolicy, + playAnimate = playAnimate, + repeatCount = repeatCount, + extra = extraData ?: EmptyExtraData, + ) +} + +fun Options(block: OptionsBuilder.() -> Unit = {}) = + OptionsBuilder().apply(block).build()