From d89f35acfa452aa3166e925fc65e2ddae8c10abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 20 Oct 2023 17:37:23 +0200 Subject: [PATCH 01/10] Refactoring service image --- .../integrationlayer/data/IlImage.kt | 37 ------- .../integrationlayer/data/ImageUrl.kt | 18 +++- .../TestDefaultImageUrlDecorator.kt | 46 ++++++++ .../TestIlHostImageUrlDecorator.kt | 72 +++++++++++++ .../integrationlayer/TestIlUrn.java | 4 +- .../TestScaleWidthImageUrlDecorator.kt | 36 +++++++ .../integrationlayer/request/ImageProvider.kt | 101 ------------------ .../request/image/DefaultImageUrlDecorator.kt | 31 ++++++ .../request/image/IlHostImageUrlDecorator.kt | 37 +++++++ .../request/image/ImageSize.kt | 22 ++++ .../request/image/ImageUrlExtension.kt | 25 +++++ .../request/image/ImageWidth.kt | 60 +++++++++++ .../image/ScaleWidthImageUrlDecorator.kt | 23 ++++ .../integrationlayer/TestImageSize.kt | 24 +++++ .../integrationlayer/TestImageWidth.kt | 98 +++++++++++++++++ 15 files changed, 490 insertions(+), 144 deletions(-) delete mode 100644 data/src/main/java/ch/srg/dataProvider/integrationlayer/data/IlImage.kt create mode 100644 dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestDefaultImageUrlDecorator.kt create mode 100644 dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestIlHostImageUrlDecorator.kt create mode 100644 dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestScaleWidthImageUrlDecorator.kt delete mode 100644 dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/ImageProvider.kt create mode 100644 dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/DefaultImageUrlDecorator.kt create mode 100644 dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/IlHostImageUrlDecorator.kt create mode 100644 dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageSize.kt create mode 100644 dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageUrlExtension.kt create mode 100644 dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageWidth.kt create mode 100644 dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ScaleWidthImageUrlDecorator.kt create mode 100644 dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/TestImageSize.kt create mode 100644 dataprovider-retrofit/src/test/java/ch/srg/dataProvider/integrationlayer/TestImageWidth.kt 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..242824c 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 @@ -19,15 +19,25 @@ data class ImageUrl( * * @return the undecorated url */ - val rawUrl: String + internal val rawUrl: String ) : Serializable { - @JvmOverloads - fun getIlImage(): IlImage { - return IlImage(rawUrl) + /** + * Url + * + * @param decorator The [ImageUrlDecorator] used to decorate the [rawUrl]. + * @param widthPixels The width of the image. + * @return The decorated [rawUrl]. + */ + fun url(decorator: ImageUrlDecorator, widthPixels: Int): String { + return decorator.decorate(rawUrl, widthPixels) } override fun toString(): String { return rawUrl } } + +interface ImageUrlDecorator { + fun decorate(source: String, size: 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..cfb7be3 --- /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.url(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.url(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.url(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.url(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..3d4543b --- /dev/null +++ b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestIlHostImageUrlDecorator.kt @@ -0,0 +1,72 @@ +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 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.url(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.url(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.url(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.url(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.url(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..97cfa81 --- /dev/null +++ b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestScaleWidthImageUrlDecorator.kt @@ -0,0 +1,36 @@ +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 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.url(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.url(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..2bc1701 --- /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 soon 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, size: 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, size) + } else { + ilHostImageUrlDecorator.decorate(imageUrl, size) + } + } +} 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..e969db2 --- /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(source: String, size: Int): String { + // Il image service only support a limited image size! + val imageWidth = ImageWidth.getFromPixels(size) + return imageServiceUri.buildUpon() + .appendQueryParameter(PARAM_IMAGE_URL, source) + .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..11aa9ff --- /dev/null +++ b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageUrlExtension.kt @@ -0,0 +1,25 @@ +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.url(widthPixels: Int, ilHost: IlHost = IlHost.PROD): String { + return url(DefaultImageUrlDecorator(ilHost), widthPixels) +} + +fun ImageUrl.url(width: ImageWidth, ilHost: IlHost = IlHost.PROD): String { + return url(widthPixels = width.widthPixels, ilHost = ilHost) +} + +fun ImageUrl.url(imageSize: ImageSize, ilHost: IlHost = IlHost.PROD): String { + return url(width = imageSize.width, ilHost = ilHost) +} + +fun ImageUrl.url(decorator: ImageUrlDecorator, width: ImageWidth): String { + return url(decorator, width.widthPixels) +} + +fun ImageUrl.url(decorator: ImageUrlDecorator, imageSize: ImageSize): String { + return url(decorator, imageSize.width) +} 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..28f8f79 --- /dev/null +++ b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageWidth.kt @@ -0,0 +1,60 @@ +package ch.srg.dataProvider.integrationlayer.request.image + +/** + * 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 = values() + var closestSize = 0 + var minDist = Int.MAX_VALUE + for (i in sizes.indices) { + val dist = Math.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..f0b34ff --- /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(source: String, size: Int): String { + return Uri.parse(source).buildUpon() + .appendPath(Scale) + .appendPath(Width) + .appendPath(size.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)) + } + +} From 22b116f2a7a55916ee00390cede55392d76b585b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Fri, 20 Oct 2023 17:44:32 +0200 Subject: [PATCH 02/10] Pull version to 0.7.0 --- buildSrc/src/main/kotlin/Config.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 02d646d..c7d6a8c 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 = 0 const val versionName = "$major.$minor.$patch" const val maven_group = "ch.srg.data.provider" From d1df1208b49dd0972564aea98c9cecbe85cbb5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 24 Oct 2023 16:28:48 +0200 Subject: [PATCH 03/10] rename param size to widthPixels more explicit --- .../ch/srg/dataProvider/integrationlayer/data/ImageUrl.kt | 2 +- .../request/image/DefaultImageUrlDecorator.kt | 6 +++--- .../request/image/IlHostImageUrlDecorator.kt | 4 ++-- .../request/image/ScaleWidthImageUrlDecorator.kt | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) 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 242824c..c9dd206 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 @@ -39,5 +39,5 @@ data class ImageUrl( } interface ImageUrlDecorator { - fun decorate(source: String, size: Int): String + fun decorate(source: String, widthPixels: Int): String } 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 index 2bc1701..1a442bf 100644 --- 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 @@ -20,12 +20,12 @@ import ch.srg.dataProvider.integrationlayer.request.IlHost class DefaultImageUrlDecorator(ilHost: IlHost = IlHost.PROD) : ImageUrlDecorator { private val ilHostImageUrlDecorator = IlHostImageUrlDecorator(ilHost) - override fun decorate(imageUrl: String, size: Int): String { + 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, size) + ScaleWidthImageUrlDecorator.decorate(imageUrl, widthPixels) } else { - ilHostImageUrlDecorator.decorate(imageUrl, size) + 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 index e969db2..aff48cf 100644 --- 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 @@ -16,9 +16,9 @@ class IlHostImageUrlDecorator(ilHost: IlHost) : ImageUrlDecorator { imageServiceUri = ilHost.hostUri.buildUpon().appendEncodedPath(IMAGES_SEGMENT).build() } - override fun decorate(source: String, size: Int): String { + override fun decorate(source: String, widthPixels: Int): String { // Il image service only support a limited image size! - val imageWidth = ImageWidth.getFromPixels(size) + val imageWidth = ImageWidth.getFromPixels(widthPixels) return imageServiceUri.buildUpon() .appendQueryParameter(PARAM_IMAGE_URL, source) .appendQueryParameter(PARAM_FORMAT, FORMAT_WEBP) 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 index f0b34ff..658ad93 100644 --- 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 @@ -12,11 +12,11 @@ object ScaleWidthImageUrlDecorator : ImageUrlDecorator { private const val Scale = "scale" private const val Width = "width" - override fun decorate(source: String, size: Int): String { + override fun decorate(source: String, widthPixels: Int): String { return Uri.parse(source).buildUpon() .appendPath(Scale) .appendPath(Width) - .appendPath(size.toString()) + .appendPath(widthPixels.toString()) .build() .toString() } From 92742b5cf6fab7fccdc7d51c46e4eeb0810c8541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Dumas?= Date: Fri, 3 Nov 2023 23:41:16 +0100 Subject: [PATCH 04/10] Cache `ImageUrlDecorator` when using `ImageUrl.url()` extensions with the same local host. --- .../request/image/ImageUrlExtension.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 index 11aa9ff..961722f 100644 --- 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 @@ -5,7 +5,8 @@ import ch.srg.dataProvider.integrationlayer.data.ImageUrlDecorator import ch.srg.dataProvider.integrationlayer.request.IlHost fun ImageUrl.url(widthPixels: Int, ilHost: IlHost = IlHost.PROD): String { - return url(DefaultImageUrlDecorator(ilHost), widthPixels) + val decorator = ImageUrlDecoratorInstances.getInstance(ilHost) + return url(decorator, widthPixels) } fun ImageUrl.url(width: ImageWidth, ilHost: IlHost = IlHost.PROD): String { @@ -23,3 +24,11 @@ fun ImageUrl.url(decorator: ImageUrlDecorator, width: ImageWidth): String { fun ImageUrl.url(decorator: ImageUrlDecorator, imageSize: ImageSize): String { return url(decorator, imageSize.width) } + +private object ImageUrlDecoratorInstances { + private val instances = mutableMapOf() + + fun getInstance(ilHost: IlHost): ImageUrlDecorator { + return instances.getOrPut(ilHost) { DefaultImageUrlDecorator(ilHost) } + } +} From 863a8dcc1c32b8a6e8e983e1d227b86c542418b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Dumas?= Date: Fri, 3 Nov 2023 23:43:11 +0100 Subject: [PATCH 05/10] Pull version to 0.7.1 --- buildSrc/src/main/kotlin/Config.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index c7d6a8c..738aea3 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -5,7 +5,7 @@ object Config { const val major = 0 const val minor = 7 - const val patch = 0 + const val patch = 1 const val versionName = "$major.$minor.$patch" const val maven_group = "ch.srg.data.provider" From d58f09ee61d2544054dc0774134e730bac2f0ead Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Loi=CC=88c=20Dumas?= Date: Sat, 4 Nov 2023 00:06:09 +0100 Subject: [PATCH 06/10] Add @JvmOverloads for ImageUrlExtensions and pull to 0.7.2 --- buildSrc/src/main/kotlin/Config.kt | 2 +- .../integrationlayer/request/image/ImageUrlExtension.kt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 738aea3..2964694 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -5,7 +5,7 @@ object Config { const val major = 0 const val minor = 7 - const val patch = 1 + const val patch = 2 const val versionName = "$major.$minor.$patch" const val maven_group = "ch.srg.data.provider" 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 index 961722f..0776073 100644 --- 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 @@ -4,15 +4,18 @@ import ch.srg.dataProvider.integrationlayer.data.ImageUrl import ch.srg.dataProvider.integrationlayer.data.ImageUrlDecorator import ch.srg.dataProvider.integrationlayer.request.IlHost +@JvmOverloads fun ImageUrl.url(widthPixels: Int, ilHost: IlHost = IlHost.PROD): String { val decorator = ImageUrlDecoratorInstances.getInstance(ilHost) return url(decorator, widthPixels) } +@JvmOverloads fun ImageUrl.url(width: ImageWidth, ilHost: IlHost = IlHost.PROD): String { return url(widthPixels = width.widthPixels, ilHost = ilHost) } +@JvmOverloads fun ImageUrl.url(imageSize: ImageSize, ilHost: IlHost = IlHost.PROD): String { return url(width = imageSize.width, ilHost = ilHost) } From 1c1833e51ac4e31bee2eda5299d22aecc5e2b47b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 28 Nov 2023 11:13:18 +0100 Subject: [PATCH 07/10] rename ImageUrl.url() to ImageUrl.decorate( --- .../integrationlayer/data/ImageUrl.kt | 21 +++++--------- .../data/ImageUrlDecorator.kt | 15 ++++++++++ .../TestDefaultImageUrlDecorator.kt | 8 ++--- .../TestIlHostImageUrlDecorator.kt | 11 +++---- .../TestScaleWidthImageUrlDecorator.kt | 5 ++-- .../request/image/IlHostImageUrlDecorator.kt | 4 +-- .../request/image/ImageUrlExtension.kt | 29 +++++++++---------- .../image/ScaleWidthImageUrlDecorator.kt | 4 +-- 8 files changed, 54 insertions(+), 43 deletions(-) create mode 100644 data/src/main/java/ch/srg/dataProvider/integrationlayer/data/ImageUrlDecorator.kt 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 c9dd206..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) @@ -23,21 +26,13 @@ data class ImageUrl( ) : Serializable { /** - * Url + * Decorated * * @param decorator The [ImageUrlDecorator] used to decorate the [rawUrl]. * @param widthPixels The width of the image. * @return The decorated [rawUrl]. */ - fun url(decorator: ImageUrlDecorator, widthPixels: Int): String { + fun decorated(decorator: ImageUrlDecorator, widthPixels: Int): String { return decorator.decorate(rawUrl, widthPixels) } - - override fun toString(): String { - return rawUrl - } -} - -interface ImageUrlDecorator { - fun decorate(source: String, widthPixels: Int): String } 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 index cfb7be3..fcc073e 100644 --- a/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestDefaultImageUrlDecorator.kt +++ b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestDefaultImageUrlDecorator.kt @@ -15,7 +15,7 @@ class TestDefaultImageUrlDecorator { 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.url(decorator, 480)) + Assert.assertEquals(expected, input.decorated(decorator, 480)) } @Test @@ -23,7 +23,7 @@ class TestDefaultImageUrlDecorator { 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.url(decorator, 480)) + Assert.assertEquals(expected, input.decorated(decorator, 480)) } @Test @@ -31,14 +31,14 @@ class TestDefaultImageUrlDecorator { 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.url(decorator, 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.url(decorator, 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 index 3d4543b..ab1e9a8 100644 --- a/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestIlHostImageUrlDecorator.kt +++ b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestIlHostImageUrlDecorator.kt @@ -7,6 +7,7 @@ import ch.srg.dataProvider.integrationlayer.request.image.IlHostImageUrlDecorato 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 @@ -19,7 +20,7 @@ class TestIlHostImageUrlDecorator { 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.url(decorator, 480)) + assertEquals(expected, input.decorated(decorator, 480)) } @Test @@ -27,7 +28,7 @@ class TestIlHostImageUrlDecorator { 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.url(decorator, 460)) + assertEquals(expected, input.decorated(decorator, 460)) } @Test @@ -35,7 +36,7 @@ class TestIlHostImageUrlDecorator { 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.url(decorator, ImageSize.MEDIUM)) + assertEquals(expected, input.decorated(decorator, ImageSize.MEDIUM)) } @Test @@ -59,7 +60,7 @@ class TestIlHostImageUrlDecorator { 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(ilHost = IlHost.STAGE, width = ImageWidth.W1920)) + assertEquals(expected, input.decorated(ilHost = IlHost.STAGE, width = ImageWidth.W1920)) } @Test @@ -67,6 +68,6 @@ class TestIlHostImageUrlDecorator { 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.url(ilHost = IlHost.TEST, imageSize = ImageSize.MEDIUM)) + assertEquals(expected, input.decorated(ilHost = IlHost.TEST, imageSize = ImageSize.MEDIUM)) } } 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 index 97cfa81..436117f 100644 --- a/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestScaleWidthImageUrlDecorator.kt +++ b/dataprovider-retrofit/src/androidTest/java/ch/srg/dataProvider/integrationlayer/TestScaleWidthImageUrlDecorator.kt @@ -5,6 +5,7 @@ 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 @@ -17,14 +18,14 @@ class TestScaleWidthImageUrlDecorator { 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.url(decorator, width)) + 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.url(decorator, ImageSize.MEDIUM)) + assertEquals(expected, input.decorated(decorator, ImageSize.MEDIUM)) } @Test 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 index aff48cf..185b337 100644 --- 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 @@ -16,11 +16,11 @@ class IlHostImageUrlDecorator(ilHost: IlHost) : ImageUrlDecorator { imageServiceUri = ilHost.hostUri.buildUpon().appendEncodedPath(IMAGES_SEGMENT).build() } - override fun decorate(source: String, widthPixels: Int): String { + 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, source) + .appendQueryParameter(PARAM_IMAGE_URL, sourceUrl) .appendQueryParameter(PARAM_FORMAT, FORMAT_WEBP) .appendQueryParameter(PARAM_WIDTH, imageWidth.widthPixels.toString()) .build() 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 index 0776073..88c7477 100644 --- 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 @@ -4,34 +4,33 @@ import ch.srg.dataProvider.integrationlayer.data.ImageUrl import ch.srg.dataProvider.integrationlayer.data.ImageUrlDecorator import ch.srg.dataProvider.integrationlayer.request.IlHost -@JvmOverloads -fun ImageUrl.url(widthPixels: Int, ilHost: IlHost = IlHost.PROD): String { - val decorator = ImageUrlDecoratorInstances.getInstance(ilHost) - return url(decorator, widthPixels) +fun ImageUrl.decorated(widthPixels: Int, ilHost: IlHost = IlHost.PROD): String { + return decorated(ImageUrlDecoratorInstances.getOrCreate(ilHost), widthPixels) } -@JvmOverloads -fun ImageUrl.url(width: ImageWidth, ilHost: IlHost = IlHost.PROD): String { - return url(widthPixels = width.widthPixels, ilHost = ilHost) +fun ImageUrl.decorated(width: ImageWidth, ilHost: IlHost = IlHost.PROD): String { + return decorated(widthPixels = width.widthPixels, ilHost = ilHost) } -@JvmOverloads -fun ImageUrl.url(imageSize: ImageSize, ilHost: IlHost = IlHost.PROD): String { - return url(width = imageSize.width, ilHost = ilHost) +fun ImageUrl.decorated(imageSize: ImageSize, ilHost: IlHost = IlHost.PROD): String { + return decorated(width = imageSize.width, ilHost = ilHost) } -fun ImageUrl.url(decorator: ImageUrlDecorator, width: ImageWidth): String { - return url(decorator, width.widthPixels) +fun ImageUrl.decorated(decorator: ImageUrlDecorator, width: ImageWidth): String { + return decorated(decorator, width.widthPixels) } -fun ImageUrl.url(decorator: ImageUrlDecorator, imageSize: ImageSize): String { - return url(decorator, imageSize.width) +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 getInstance(ilHost: IlHost): ImageUrlDecorator { + 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/ScaleWidthImageUrlDecorator.kt b/dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ScaleWidthImageUrlDecorator.kt index 658ad93..074c5cb 100644 --- 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 @@ -12,8 +12,8 @@ object ScaleWidthImageUrlDecorator : ImageUrlDecorator { private const val Scale = "scale" private const val Width = "width" - override fun decorate(source: String, widthPixels: Int): String { - return Uri.parse(source).buildUpon() + override fun decorate(sourceUrl: String, widthPixels: Int): String { + return Uri.parse(sourceUrl).buildUpon() .appendPath(Scale) .appendPath(Width) .appendPath(widthPixels.toString()) From 3dbbdfad13ded283c89f2641a4b5180d0a6383ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 28 Nov 2023 13:59:23 +0100 Subject: [PATCH 08/10] Update dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/DefaultImageUrlDecorator.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gaëtan Muller --- .../integrationlayer/request/image/DefaultImageUrlDecorator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 1a442bf..ac209c8 100644 --- 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 @@ -13,7 +13,7 @@ import ch.srg.dataProvider.integrationlayer.request.IlHost /** * Default image url decorator * - * For specific RTS image url the old [ScaleWidthImageUrlDecorator] is used, but it should be fixed soon or later. + * 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]. */ From 30d98b4e0f66bdfbadf3ce1715eab2bbe76656cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 28 Nov 2023 14:01:18 +0100 Subject: [PATCH 09/10] Update dataprovider-retrofit/src/main/java/ch/srg/dataProvider/integrationlayer/request/image/ImageWidth.kt MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Gaëtan Muller --- .../dataProvider/integrationlayer/request/image/ImageWidth.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 28f8f79..7a05995 100644 --- 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 @@ -44,7 +44,7 @@ enum class ImageWidth(val widthPixels: Int) { if (widthPixels <= W240.widthPixels) { return W240 } - val sizes = values() + val sizes = entries var closestSize = 0 var minDist = Int.MAX_VALUE for (i in sizes.indices) { From 74a9bd375e3c3a392c19d4f0ff34ab08e3034180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joaquim=20St=C3=A4hli?= Date: Tue, 28 Nov 2023 14:02:04 +0100 Subject: [PATCH 10/10] Use kotlin version of abs --- .../dataProvider/integrationlayer/request/image/ImageWidth.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 index 7a05995..c1c38c8 100644 --- 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 @@ -1,5 +1,7 @@ package ch.srg.dataProvider.integrationlayer.request.image +import kotlin.math.abs + /** * Image width supported by the integration layer * @@ -48,7 +50,7 @@ enum class ImageWidth(val widthPixels: Int) { var closestSize = 0 var minDist = Int.MAX_VALUE for (i in sizes.indices) { - val dist = Math.abs(sizes[i].widthPixels - widthPixels) + val dist = abs(sizes[i].widthPixels - widthPixels) if (dist <= minDist) { minDist = dist closestSize = i