diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 02d646d..2964694 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -4,8 +4,8 @@ object Config { const val minSdk = 21 const val major = 0 - const val minor = 6 - const val patch = 3 + const val minor = 7 + const val patch = 2 const val versionName = "$major.$minor.$patch" const val maven_group = "ch.srg.data.provider" diff --git a/data/src/main/java/ch/srg/dataProvider/integrationlayer/data/IlImage.kt b/data/src/main/java/ch/srg/dataProvider/integrationlayer/data/IlImage.kt deleted file mode 100644 index 1307f55..0000000 --- a/data/src/main/java/ch/srg/dataProvider/integrationlayer/data/IlImage.kt +++ /dev/null @@ -1,37 +0,0 @@ -package ch.srg.dataProvider.integrationlayer.data - -/** - * Copyright (c) SRG SSR. All rights reserved. - * - * - * License information is available from the LICENSE file. - */ -data class IlImage @JvmOverloads constructor(val url: String) { - - @Suppress("MagicNumber") - enum class Size(val sizePixels: Int) { - W240(240), W320(320), W480(480), W960(960), W1920(1920); - - companion object { - fun getClosest(pixels: Int): Size { - if (pixels >= W1920.sizePixels) { - return W1920 - } - if (pixels <= W240.sizePixels) { - return W240 - } - val sizes = values() - var closestSize = 0 - var minDist = Int.MAX_VALUE - for (i in sizes.indices) { - val dist = Math.abs(sizes[i].sizePixels - pixels) - if (dist <= minDist) { - minDist = dist - closestSize = i - } - } - return sizes[closestSize] - } - } - } -} diff --git a/data/src/main/java/ch/srg/dataProvider/integrationlayer/data/ImageUrl.kt b/data/src/main/java/ch/srg/dataProvider/integrationlayer/data/ImageUrl.kt index 17a3b62..b2b6714 100644 --- a/data/src/main/java/ch/srg/dataProvider/integrationlayer/data/ImageUrl.kt +++ b/data/src/main/java/ch/srg/dataProvider/integrationlayer/data/ImageUrl.kt @@ -1,3 +1,7 @@ +/* + * Copyright (c) SRG SSR. All rights reserved. + * License information is available from the LICENSE file. + */ @file:Suppress("MemberVisibilityCanBePrivate") package ch.srg.dataProvider.integrationlayer.data @@ -6,10 +10,9 @@ import ch.srg.dataProvider.integrationlayer.data.serializer.ImageUrlSerializer import java.io.Serializable /** - * Copyright (c) SRG SSR. All rights reserved. - * + * Image url * - * License information is available from the LICENSE file. + * @property rawUrl Internal image url, to retrieve the url use [ImageUrl.decorated]. */ @Suppress("SerialVersionUIDInSerializableClass") @kotlinx.serialization.Serializable(with = ImageUrlSerializer::class) @@ -19,15 +22,17 @@ data class ImageUrl( * * @return the undecorated url */ - val rawUrl: String + internal val rawUrl: String ) : Serializable { - @JvmOverloads - fun getIlImage(): IlImage { - return IlImage(rawUrl) - } - - override fun toString(): String { - return rawUrl + /** + * Decorated + * + * @param decorator The [ImageUrlDecorator] used to decorate the [rawUrl]. + * @param widthPixels The width of the image. + * @return The decorated [rawUrl]. + */ + fun decorated(decorator: ImageUrlDecorator, widthPixels: Int): String { + return decorator.decorate(rawUrl, widthPixels) } } diff --git a/data/src/main/java/ch/srg/dataProvider/integrationlayer/data/ImageUrlDecorator.kt b/data/src/main/java/ch/srg/dataProvider/integrationlayer/data/ImageUrlDecorator.kt new file mode 100644 index 0000000..4562f08 --- /dev/null +++ b/data/src/main/java/ch/srg/dataProvider/integrationlayer/data/ImageUrlDecorator.kt @@ -0,0 +1,15 @@ +package ch.srg.dataProvider.integrationlayer.data + +/** + * Image url decorator + */ +interface ImageUrlDecorator { + /** + * Decorate [sourceUrl] with [widthPixels]. + * + * @param sourceUrl The source url. + * @param widthPixels The width size in pixels. + * @return decorated url. + */ + fun decorate(sourceUrl: String, widthPixels: Int): String +} diff --git a/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestDefaultImageUrlDecorator.kt b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestDefaultImageUrlDecorator.kt new file mode 100644 index 0000000..fcc073e --- /dev/null +++ b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestDefaultImageUrlDecorator.kt @@ -0,0 +1,46 @@ +package ch.srg.dataProvider.integrationlayer + +import android.net.Uri +import ch.srg.dataProvider.integrationlayer.data.ImageUrl +import ch.srg.dataProvider.integrationlayer.request.IlHost +import ch.srg.dataProvider.integrationlayer.request.image.DefaultImageUrlDecorator +import org.junit.Assert +import org.junit.Test + +class TestDefaultImageUrlDecorator { + private val decorator = DefaultImageUrlDecorator(ilHost = IlHost.PROD) + + @Test + fun testNonRtsUrl() { + val input = ImageUrl("https://ws.srf.ch/asset/image/audio/123") + val encodedInput = Uri.encode("https://ws.srf.ch/asset/image/audio/123") + val expected = "https://il.srgssr.ch/images/?imageUrl=${encodedInput}&format=webp&width=480" + Assert.assertEquals(expected, input.decorated(decorator, 480)) + } + + @Test + fun testRtsUrlWithoutImage() { + val input = ImageUrl("https://ws.rts.ch/asset/image/audio/123") + val encodedInput = Uri.encode("https://ws.rts.ch/asset/image/audio/123") + val expected = "https://il.srgssr.ch/images/?imageUrl=${encodedInput}&format=webp&width=480" + Assert.assertEquals(expected, input.decorated(decorator, 480)) + } + + @Test + fun testUrlWithImageOnly() { + val input = ImageUrl("https://ws.srf.ch/asset/image/audio/123.image") + val encodedInput = Uri.encode("https://ws.srf.ch/asset/image/audio/123.image") + val expected = "https://il.srgssr.ch/images/?imageUrl=${encodedInput}&format=webp&width=480" + Assert.assertEquals(expected, input.decorated(decorator, 480)) + } + + @Test + fun testRtsUrlWithImage() { + val input = ImageUrl("https://ws.rts.ch/asset/image/audio/123.image") + val expected = "https://ws.rts.ch/asset/image/audio/123.image/scale/width/460" + Assert.assertEquals(expected, input.decorated(decorator, 460)) + } + + + +} diff --git a/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestIlHostImageUrlDecorator.kt b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestIlHostImageUrlDecorator.kt new file mode 100644 index 0000000..ab1e9a8 --- /dev/null +++ b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestIlHostImageUrlDecorator.kt @@ -0,0 +1,73 @@ +package ch.srg.dataProvider.integrationlayer + +import android.net.Uri +import ch.srg.dataProvider.integrationlayer.data.ImageUrl +import ch.srg.dataProvider.integrationlayer.request.IlHost +import ch.srg.dataProvider.integrationlayer.request.image.IlHostImageUrlDecorator +import ch.srg.dataProvider.integrationlayer.request.image.ImageSize +import ch.srg.dataProvider.integrationlayer.request.image.ImageWidth +import ch.srg.dataProvider.integrationlayer.request.image.url +import ch.srg.dataProvider.integrationlayer.request.image.decorated +import org.junit.Assert.assertEquals +import org.junit.Test + +class TestIlHostImageUrlDecorator { + + private val decorator = IlHostImageUrlDecorator(ilHost = IlHost.PROD) + + @Test + fun testPixelValid() { + val input = ImageUrl("https://ws.srf.ch/asset/image/audio/123") + val encodedInput = Uri.encode("https://ws.srf.ch/asset/image/audio/123") + val expected = "https://il.srgssr.ch/images/?imageUrl=${encodedInput}&format=webp&width=480" + assertEquals(expected, input.decorated(decorator, 480)) + } + + @Test + fun testPixelWidthInvalid() { + val input = ImageUrl("https://ws.srf.ch/asset/image/audio/123") + val encodedInput = Uri.encode("https://ws.srf.ch/asset/image/audio/123") + val expected = "https://il.srgssr.ch/images/?imageUrl=${encodedInput}&format=webp&width=480" + assertEquals(expected, input.decorated(decorator, 460)) + } + + @Test + fun testImageSize() { + val input = ImageUrl("https://ws.srf.ch/asset/image/audio/123") + val encodedInput = Uri.encode("https://ws.srf.ch/asset/image/audio/123") + val expected = "https://il.srgssr.ch/images/?imageUrl=${encodedInput}&format=webp&width=480" + assertEquals(expected, input.decorated(decorator, ImageSize.MEDIUM)) + } + + @Test + fun testImageWidth() { + val input = ImageUrl("https://ws.srf.ch/asset/image/audio/123") + val encodedInput = Uri.encode("https://ws.srf.ch/asset/image/audio/123") + val expected = "https://il.srgssr.ch/images/?imageUrl=${encodedInput}&format=webp&width=1920" + assertEquals(expected, input.url(decorator, ImageWidth.W1920)) + } + + @Test + fun testOtherIlHost() { + val input = ImageUrl("https://ws.srf.ch/asset/image/audio/123") + val encodedInput = Uri.encode("https://ws.srf.ch/asset/image/audio/123") + val expected = "https://il-stage.srgssr.ch/images/?imageUrl=${encodedInput}&format=webp&width=1920" + assertEquals(expected, input.url(decorator = IlHostImageUrlDecorator(IlHost.STAGE), width = ImageWidth.W1920)) + } + + @Test + fun testExtensionImageWidthWithIlHost() { + val input = ImageUrl("https://ws.srf.ch/asset/image/audio/123") + val encodedInput = Uri.encode("https://ws.srf.ch/asset/image/audio/123") + val expected = "https://il-stage.srgssr.ch/images/?imageUrl=${encodedInput}&format=webp&width=1920" + assertEquals(expected, input.decorated(ilHost = IlHost.STAGE, width = ImageWidth.W1920)) + } + + @Test + fun testExtensionImageSizeWithIlHost() { + val input = ImageUrl("https://ws.srf.ch/asset/image/audio/123") + val encodedInput = Uri.encode("https://ws.srf.ch/asset/image/audio/123") + val expected = "https://il-test.srgssr.ch/images/?imageUrl=${encodedInput}&format=webp&width=480" + assertEquals(expected, input.decorated(ilHost = IlHost.TEST, imageSize = ImageSize.MEDIUM)) + } +} diff --git a/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestIlUrn.java b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestIlUrn.java index 3ab8223..6f17b2c 100644 --- a/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestIlUrn.java +++ b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestIlUrn.java @@ -40,7 +40,7 @@ public void createFromValidUrn() { } @Test - public void testIsAudioValideUrn() { + public void testIsAudioValidUrn() { Assert.assertFalse(IlUrn.isAudio("urn:rts:video:123456")); Assert.assertTrue(IlUrn.isAudio("urn:rts:audio:123456")); Assert.assertFalse(IlUrn.isAudio("urn:a:b:12345")); @@ -58,7 +58,7 @@ public void testIsAudioNullUrn() { @Test - public void testIsVideoValideUrn() { + public void testIsVideoValidUrn() { Assert.assertTrue(IlUrn.isVideo("urn:rts:video:123456")); Assert.assertFalse(IlUrn.isVideo("urn:rts:audio:123456")); Assert.assertFalse(IlUrn.isVideo("urn:a:b:12345")); diff --git a/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestScaleWidthImageUrlDecorator.kt b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestScaleWidthImageUrlDecorator.kt new file mode 100644 index 0000000..436117f --- /dev/null +++ b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestScaleWidthImageUrlDecorator.kt @@ -0,0 +1,37 @@ +package ch.srg.dataProvider.integrationlayer + +import ch.srg.dataProvider.integrationlayer.data.ImageUrl +import ch.srg.dataProvider.integrationlayer.request.image.ImageSize +import ch.srg.dataProvider.integrationlayer.request.image.ImageWidth +import ch.srg.dataProvider.integrationlayer.request.image.ScaleWidthImageUrlDecorator +import ch.srg.dataProvider.integrationlayer.request.image.url +import ch.srg.dataProvider.integrationlayer.request.image.decorated +import org.junit.Assert.assertEquals +import org.junit.Test + +class TestScaleWidthImageUrlDecorator { + + private val decorator = ScaleWidthImageUrlDecorator + + @Test + fun testScaleWidth() { + val input = ImageUrl("https://www.data.com/images/images.png") + val width = 458 + val expected = "https://www.data.com/images/images.png/scale/width/458" + assertEquals(expected, input.decorated(decorator, width)) + } + + @Test + fun testScaleWidthImageSize() { + val input = ImageUrl("https://www.data.com/images/images.png") + val expected = "https://www.data.com/images/images.png/scale/width/480" + assertEquals(expected, input.decorated(decorator, ImageSize.MEDIUM)) + } + + @Test + fun testScaleWidthImageWidth() { + val input = ImageUrl("https://www.data.com/images/images.png") + val expected = "https://www.data.com/images/images.png/scale/width/1920" + assertEquals(expected, input.url(decorator, ImageWidth.W1920)) + } +} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/ImageProvider.kt b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/ImageProvider.kt deleted file mode 100644 index 41ca10f..0000000 --- a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/ImageProvider.kt +++ /dev/null @@ -1,101 +0,0 @@ -package ch.srg.dataProvider.integrationlayer.request - -import android.net.Uri -import android.text.TextUtils -import androidx.annotation.IntDef -import ch.srg.dataProvider.integrationlayer.SRGUrlFactory -import ch.srg.dataProvider.integrationlayer.data.IlImage - -/** - * Copyright (c) SRG SSR. All rights reserved. - * - * - * License information is available from the LICENSE file. - */ -class ImageProvider(factory: SRGUrlFactory) { - @IntDef(SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE) - annotation class ImageSize - - private val srgImageServiceUri: Uri - - init { - srgImageServiceUri = factory.hostUri.buildUpon().appendEncodedPath("images/").build() - } - - fun decorateImageWithSize(image: IlImage, @ImageSize size: Int): Uri? { - return decorateImageWithSize(image, getImageSize(size)) - } - - fun decorateImageWithSize(image: IlImage, size: IlImage.Size): Uri? { - return decorateImageWithSizeInPixel(image, size.sizePixels) - } - - /** - * @param widthInPixels 160,240,320,480,640,960,1280,1920 - */ - private fun decorateImageWithSizeInPixel(image: IlImage, widthInPixels: Int): Uri? { - return decorateImageUrlWithSizeInPixel(image.url, widthInPixels) - } - - fun decorateImageUrlWithSize(imageUrl: String, @ImageSize size: Int): Uri? { - return decorateImageUrlWithSize(imageUrl, getImageSize(size)) - } - - fun decorateImageUrlWithSize(imageUrl: String, size: IlImage.Size): Uri? { - return decorateImageUrlWithSizeInPixel(imageUrl, size.sizePixels) - } - - /** - * Fixme https://github.com/SRGSSR/srgdataprovider-apple/issues/47 once RTS image service is well connected to Il Play image service. - * - * @param widthInPixels 160,240,320,480,640,960,1280,1920 - */ - private fun decorateImageUrlWithSizeInPixel(imageUrl: String, widthInPixels: Int): Uri? { - return if (TextUtils.isEmpty(imageUrl)) { - null - } else { - if (imageUrl.contains("rts.ch") && imageUrl.contains(".image")) { - return createBusinessUnitImageServiceUrl(imageUrl, widthInPixels) - } else { - return createPlaySrgImageServiceUrl(imageUrl, widthInPixels) - } - } - } - - private fun createPlaySrgImageServiceUrl(imageUrl: String?, width: Int): Uri { - return srgImageServiceUri.buildUpon() - .appendQueryParameter("imageUrl", imageUrl) - .appendQueryParameter("format", FORMAT_WEBP) - .appendQueryParameter("width", width.toString()) - .build() - } - - private fun addScaleWith(uri: Uri.Builder, width: Int): Uri.Builder { - return uri.appendPath(Scale).appendPath(Width).appendPath(width.toString()) - } - - private fun createBusinessUnitImageServiceUrl(url: String?, width: Int): Uri { - return addScaleWith(Uri.parse(url).buildUpon(), width).build() - } - - private fun getImageSize(@ImageSize size: Int): IlImage.Size { - return DIMENSIONS_PX[size] - } - - companion object { - const val SIZE_SMALL = 0 - const val SIZE_MEDIUM = 1 - const val SIZE_LARGE = 2 - - /** - * Dimension to use for each size depending of screen density - * (SRGImageSizeSmall) : @(SRGImageWidth320), - * (SRGImageSizeMedium) : @(SRGImageWidth480), - * (SRGImageSizeLarge) : @(SRGImageWidth960) - */ - private val DIMENSIONS_PX = arrayOf(IlImage.Size.W320, IlImage.Size.W480, IlImage.Size.W960) - private const val Scale = "scale" - private const val Width = "width" - private const val FORMAT_WEBP = "webp" // webp, jpg, png - } -} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/DefaultImageUrlDecorator.kt b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/DefaultImageUrlDecorator.kt new file mode 100644 index 0000000..ac209c8 --- /dev/null +++ b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/DefaultImageUrlDecorator.kt @@ -0,0 +1,31 @@ +package ch.srg.dataProvider.integrationlayer.request.image + +import ch.srg.dataProvider.integrationlayer.data.ImageUrlDecorator +import ch.srg.dataProvider.integrationlayer.request.IlHost + +/** + * Copyright (c) SRG SSR. All rights reserved. + * + * + * License information is available from the LICENSE file. + */ + +/** + * Default image url decorator + * + * For specific RTS image url, the old [ScaleWidthImageUrlDecorator] is used, but it should be fixed sooner or later. + * * + * @param ilHost The [IlHost] to use with [ilHostImageUrlDecorator]. + */ +class DefaultImageUrlDecorator(ilHost: IlHost = IlHost.PROD) : ImageUrlDecorator { + private val ilHostImageUrlDecorator = IlHostImageUrlDecorator(ilHost) + + override fun decorate(imageUrl: String, widthPixels: Int): String { + // FIXME https://github.com/SRGSSR/srgdataprovider-apple/issues/47 once RTS image service is well connected to Il Play image service. + return if (imageUrl.contains("rts.ch") && imageUrl.contains(".image")) { + ScaleWidthImageUrlDecorator.decorate(imageUrl, widthPixels) + } else { + ilHostImageUrlDecorator.decorate(imageUrl, widthPixels) + } + } +} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/IlHostImageUrlDecorator.kt b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/IlHostImageUrlDecorator.kt new file mode 100644 index 0000000..185b337 --- /dev/null +++ b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/IlHostImageUrlDecorator.kt @@ -0,0 +1,37 @@ +package ch.srg.dataProvider.integrationlayer.request.image + +import android.net.Uri +import ch.srg.dataProvider.integrationlayer.data.ImageUrlDecorator +import ch.srg.dataProvider.integrationlayer.request.IlHost + +/** + * Il host image url decorator + * + * @param ilHost The [IlHost] of the integration layer image service. + */ +class IlHostImageUrlDecorator(ilHost: IlHost) : ImageUrlDecorator { + private val imageServiceUri: Uri + + init { + imageServiceUri = ilHost.hostUri.buildUpon().appendEncodedPath(IMAGES_SEGMENT).build() + } + + override fun decorate(sourceUrl: String, widthPixels: Int): String { + // Il image service only support a limited image size! + val imageWidth = ImageWidth.getFromPixels(widthPixels) + return imageServiceUri.buildUpon() + .appendQueryParameter(PARAM_IMAGE_URL, sourceUrl) + .appendQueryParameter(PARAM_FORMAT, FORMAT_WEBP) + .appendQueryParameter(PARAM_WIDTH, imageWidth.widthPixels.toString()) + .build() + .toString() + } + + companion object { + private const val FORMAT_WEBP = "webp" // webp, jpg, png + private const val IMAGES_SEGMENT = "images/" + private const val PARAM_IMAGE_URL = "imageUrl" + private const val PARAM_FORMAT = "format" + private const val PARAM_WIDTH = "width" + } +} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageSize.kt b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageSize.kt new file mode 100644 index 0000000..c33a06a --- /dev/null +++ b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageSize.kt @@ -0,0 +1,22 @@ +package ch.srg.dataProvider.integrationlayer.request.image + +/** + * Image size + * @property width [ImageWidth]. + */ +enum class ImageSize(val width: ImageWidth) { + /** + * Small [ImageWidth.W320] + */ + SMALL(ImageWidth.W320), + + /** + * Medium [ImageWidth.W480] + */ + MEDIUM(ImageWidth.W480), + + /** + * Large [ImageWidth.W960] + */ + LARGE(ImageWidth.W960), +} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageUrlExtension.kt b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageUrlExtension.kt new file mode 100644 index 0000000..88c7477 --- /dev/null +++ b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageUrlExtension.kt @@ -0,0 +1,36 @@ +package ch.srg.dataProvider.integrationlayer.request.image + +import ch.srg.dataProvider.integrationlayer.data.ImageUrl +import ch.srg.dataProvider.integrationlayer.data.ImageUrlDecorator +import ch.srg.dataProvider.integrationlayer.request.IlHost + +fun ImageUrl.decorated(widthPixels: Int, ilHost: IlHost = IlHost.PROD): String { + return decorated(ImageUrlDecoratorInstances.getOrCreate(ilHost), widthPixels) +} + +fun ImageUrl.decorated(width: ImageWidth, ilHost: IlHost = IlHost.PROD): String { + return decorated(widthPixels = width.widthPixels, ilHost = ilHost) +} + +fun ImageUrl.decorated(imageSize: ImageSize, ilHost: IlHost = IlHost.PROD): String { + return decorated(width = imageSize.width, ilHost = ilHost) +} + +fun ImageUrl.decorated(decorator: ImageUrlDecorator, width: ImageWidth): String { + return decorated(decorator, width.widthPixels) +} + +fun ImageUrl.decorated(decorator: ImageUrlDecorator, imageSize: ImageSize): String { + return decorated(decorator, imageSize.width) +} + +/** + * Optimization for extensions to reuse DefaultImageUrlDecorator based on IlHost. + */ +private object ImageUrlDecoratorInstances { + private val instances = mutableMapOf() + + fun getOrCreate(ilHost: IlHost): ImageUrlDecorator { + return instances.getOrPut(ilHost) { DefaultImageUrlDecorator(ilHost) } + } +} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageWidth.kt b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageWidth.kt new file mode 100644 index 0000000..c1c38c8 --- /dev/null +++ b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageWidth.kt @@ -0,0 +1,62 @@ +package ch.srg.dataProvider.integrationlayer.request.image + +import kotlin.math.abs + +/** + * Image width supported by the integration layer + * + * @property widthPixels The width in pixels. + */ +@Suppress("MagicNumber") +enum class ImageWidth(val widthPixels: Int) { + W240(240), + W320(320), + W480(480), + W960(960), + W1920(1920), + ; + + companion object { + + /** + * Get the [ImageWidth] that matches [pixels] or the closest one. + * + * @param pixels The width in pixels. + */ + fun getFromPixels(pixels: Int): ImageWidth { + return when (pixels) { + W240.widthPixels -> W240 + W320.widthPixels -> W320 + W480.widthPixels -> W480 + W960.widthPixels -> W960 + W1920.widthPixels -> W1920 + else -> getClosest(pixels) + } + } + + /** + * Get closest [ImageWidth] + * + * @param widthPixels The width in pixels to get the closest [ImageWidth]. + */ + private fun getClosest(widthPixels: Int): ImageWidth { + if (widthPixels >= W1920.widthPixels) { + return W1920 + } + if (widthPixels <= W240.widthPixels) { + return W240 + } + val sizes = entries + var closestSize = 0 + var minDist = Int.MAX_VALUE + for (i in sizes.indices) { + val dist = abs(sizes[i].widthPixels - widthPixels) + if (dist <= minDist) { + minDist = dist + closestSize = i + } + } + return sizes[closestSize] + } + } +} diff --git a/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ScaleWidthImageUrlDecorator.kt b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ScaleWidthImageUrlDecorator.kt new file mode 100644 index 0000000..074c5cb --- /dev/null +++ b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ScaleWidthImageUrlDecorator.kt @@ -0,0 +1,23 @@ +package ch.srg.dataProvider.integrationlayer.request.image + +import android.net.Uri +import ch.srg.dataProvider.integrationlayer.data.ImageUrlDecorator + +/** + * Scale width image url decorator + * + * @constructor Create empty Scale width image url decorator + */ +object ScaleWidthImageUrlDecorator : ImageUrlDecorator { + private const val Scale = "scale" + private const val Width = "width" + + override fun decorate(sourceUrl: String, widthPixels: Int): String { + return Uri.parse(sourceUrl).buildUpon() + .appendPath(Scale) + .appendPath(Width) + .appendPath(widthPixels.toString()) + .build() + .toString() + } +} diff --git a/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/TestImageSize.kt b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/TestImageSize.kt new file mode 100644 index 0000000..ce9251d --- /dev/null +++ b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/TestImageSize.kt @@ -0,0 +1,24 @@ +package ch.srg.dataProvider.integrationlayer + +import ch.srg.dataProvider.integrationlayer.request.image.ImageSize +import ch.srg.dataProvider.integrationlayer.request.image.ImageWidth +import org.junit.Assert +import org.junit.Test + +class TestImageSize { + + @Test + fun testSmallSizeIsW320() { + Assert.assertEquals(ImageWidth.W320, ImageSize.SMALL.width) + } + + @Test + fun testMediumSizeIsW480() { + Assert.assertEquals(ImageWidth.W480, ImageSize.MEDIUM.width) + } + + @Test + fun testLargeSizeIsW960() { + Assert.assertEquals(ImageWidth.W960, ImageSize.LARGE.width) + } +} diff --git a/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/TestImageWidth.kt b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/TestImageWidth.kt new file mode 100644 index 0000000..25f9cc6 --- /dev/null +++ b/dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/TestImageWidth.kt @@ -0,0 +1,98 @@ +package ch.srg.dataProvider.integrationlayer + +import ch.srg.dataProvider.integrationlayer.request.image.ImageWidth +import org.junit.Assert.assertEquals +import org.junit.Test + +class TestImageWidth { + + @Test + fun testGetFromPixelMatchingWidth() { + assertEquals(ImageWidth.W240, ImageWidth.getFromPixels(240)) + assertEquals(ImageWidth.W320, ImageWidth.getFromPixels(320)) + assertEquals(ImageWidth.W480, ImageWidth.getFromPixels(480)) + assertEquals(ImageWidth.W960, ImageWidth.getFromPixels(960)) + assertEquals(ImageWidth.W1920, ImageWidth.getFromPixels(1920)) + } + + @Test + fun testClosestUnder240Pixels() { + assertEquals(ImageWidth.W240, ImageWidth.getFromPixels(0)) + assertEquals(ImageWidth.W240, ImageWidth.getFromPixels(239)) + } + + @Test + fun testClosestGreaterThan1920Pixels() { + assertEquals(ImageWidth.W1920, ImageWidth.getFromPixels(1921)) + assertEquals(ImageWidth.W1920, ImageWidth.getFromPixels(4000)) + } + + @Test + fun testClosestW240() { + assertEquals(ImageWidth.W240, ImageWidth.getFromPixels(239)) + assertEquals(ImageWidth.W240, ImageWidth.getFromPixels(240)) + assertEquals(ImageWidth.W240, ImageWidth.getFromPixels(241)) + } + + @Test + fun testClosestW320() { + assertEquals(ImageWidth.W320, ImageWidth.getFromPixels(319)) + assertEquals(ImageWidth.W320, ImageWidth.getFromPixels(320)) + assertEquals(ImageWidth.W320, ImageWidth.getFromPixels(321)) + } + + @Test + fun testClosestW480() { + assertEquals(ImageWidth.W480, ImageWidth.getFromPixels(479)) + assertEquals(ImageWidth.W480, ImageWidth.getFromPixels(480)) + assertEquals(ImageWidth.W480, ImageWidth.getFromPixels(481)) + } + + @Test + fun testClosestW960() { + assertEquals(ImageWidth.W960, ImageWidth.getFromPixels(959)) + assertEquals(ImageWidth.W960, ImageWidth.getFromPixels(960)) + assertEquals(ImageWidth.W960, ImageWidth.getFromPixels(961)) + } + + @Test + fun testClosestW1920() { + assertEquals(ImageWidth.W1920, ImageWidth.getFromPixels(1919)) + assertEquals(ImageWidth.W1920, ImageWidth.getFromPixels(1920)) + assertEquals(ImageWidth.W1920, ImageWidth.getFromPixels(1921)) + } + + @Test + fun testClosestBetween240_320() { + assertEquals(ImageWidth.W240, ImageWidth.getFromPixels(279)) + assertEquals(ImageWidth.W320, ImageWidth.getFromPixels(280)) // Middle + assertEquals(ImageWidth.W320, ImageWidth.getFromPixels(281)) + } + + @Test + fun testClosestBetween320_480() { + assertEquals(ImageWidth.W320, ImageWidth.getFromPixels(399)) + assertEquals(ImageWidth.W480, ImageWidth.getFromPixels(400)) // Middle + assertEquals(ImageWidth.W480, ImageWidth.getFromPixels(401)) + } + + @Test + fun testClosestBetween480_960() { + assertEquals(ImageWidth.W480, ImageWidth.getFromPixels(719)) + assertEquals(ImageWidth.W960, ImageWidth.getFromPixels(720)) // Middle + assertEquals(ImageWidth.W960, ImageWidth.getFromPixels(721)) + } + + @Test + fun testClosestBetween960_1920() { + assertEquals(ImageWidth.W960, ImageWidth.getFromPixels(1439)) + assertEquals(ImageWidth.W1920, ImageWidth.getFromPixels(1440)) // Middle + assertEquals(ImageWidth.W1920, ImageWidth.getFromPixels(1441)) + } + + @Test + fun testNegativeWidthPixels() { + assertEquals(ImageWidth.W240, ImageWidth.getFromPixels(-100)) + } + +}