diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ad29045..678a98f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +### Next release +- Fix: Align mobile SDK size recommendation API requests with web implementation + ### 2.12.2 - Fix: Display proper size recommendation text diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParams.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParams.kt index 2c1d22d2..8e532a9e 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParams.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParams.kt @@ -26,6 +26,9 @@ internal data class BodyProfileRecommendedSizeParams( */ fun paramsToMap(): Map { return emptyMap() + .plus( + mapOf("app_origin" to APP_ORIGIN_ANDROID), + ) .plus( mapOf(PARAM_BODY_DATA to createBodyDataParams()), ) @@ -47,8 +50,40 @@ internal data class BodyProfileRecommendedSizeParams( ) } - private fun createItemsParams(): Map { - return emptyMap() + fun paramsToMapShoe(): Map { + return emptyMap() + .plus( + mapOf(PARAM_BODY_DATA to createBodyDataParams()), + ) + .plus( + mapOf(PARAM_USER_GENDER to userBodyProfile.gender), + ) + .plus( + mapOf(PARAM_USER_HEIGHT to userBodyProfile.height), + ) + .plus( + userBodyProfile.weight.toFloatOrNull()?.let { mapOf(PARAM_USER_WEIGHT to it) } + .orEmpty(), + ) + .plus( + mapOf(PARAM_USER_AGE to userBodyProfile.age), + ) + .plus( + createItemsParams(), + ) + .plus( + mapOf(PARAM_FOOTWEAR_DATA to userBodyProfile.footwearData), + ) + .plus( + mapOf(PARAM_ADDITIONAL_INFO to createAdditionalInfoParams()), + ) + .plus( + mapOf(PARAM_PRODUCT_NAME to storeProduct.name), + ) + } + + private fun createItemsParams(): Map { + return emptyMap() .plus( mapOf(PARAM_ITEM_SIZES to createItemSizesParams()), ) @@ -80,6 +115,10 @@ internal data class BodyProfileRecommendedSizeParams( measurement.name to measurement.millimeter } } + val modelInfo = + storeProduct.storeProductMeta?.additionalInfo?.modelInfo + ?.mapKeys { (key, _) -> toSnakeCase(key) } ?: JSONObject.NULL + return emptyMap() .plus( mapOf(PARAM_BRAND to (brand ?: "")), @@ -93,13 +132,10 @@ internal data class BodyProfileRecommendedSizeParams( mapOf(PARAM_SIZES to (sizeHashMap ?: mutableMapOf())), ) .plus( - mapOf( - PARAM_MODEL_INFO to - (storeProduct.storeProductMeta?.additionalInfo?.modelInfo ?: JSONObject.NULL), - ), + mapOf(PARAM_MODEL_INFO to modelInfo), ) .plus( - mapOf(PARAM_GENDER to userBodyProfile.gender), + mapOf(PARAM_GENDER to (storeProduct.storeProductMeta?.additionalInfo?.gender ?: "male")), ) .plus( mapOf( @@ -137,6 +173,8 @@ internal data class BodyProfileRecommendedSizeParams( ) }.orEmpty(), ) + // Convert all keys in the map to snake_case + .mapKeys { (key, _) -> toSnakeCase(key) } } /** @@ -155,21 +193,23 @@ internal data class BodyProfileRecommendedSizeParams( } private companion object { - const val PARAM_ADDITIONAL_INFO = "additionalInfo" - const val PARAM_BODY_DATA = "bodyData" - const val PARAM_ITEM_SIZES = "itemSizesOrig" + const val PARAM_ADDITIONAL_INFO = "additional_info" + const val PARAM_BODY_DATA = "body_data" + const val PARAM_ITEM_SIZES = "item_sizes_orig" const val PARAM_ITEMS = "items" - const val PARAM_PRODUCT_TYPE = "productType" - const val PARAM_USER_GENDER = "userGender" - const val PARAM_USER_HEIGHT = "userHeight" - const val PARAM_USER_WEIGHT = "userWeight" - const val PARAM_USER_AGE = "userAge" - const val PARAM_EXTERNAL_PRODUCT_ID = "extProductId" + const val PARAM_PRODUCT_TYPE = "product_type" + const val PARAM_USER_GENDER = "user_gender" + const val PARAM_USER_HEIGHT = "user_height" + const val PARAM_USER_WEIGHT = "user_weight" + const val PARAM_USER_AGE = "user_age" + const val PARAM_EXTERNAL_PRODUCT_ID = "ext_product_id" + const val PARAM_FOOTWEAR_DATA = "footwear_data" + const val PARAM_PRODUCT_NAME = "product_name" const val PARAM_BRAND = "brand" const val PARAM_FIT = "fit" const val PARAM_SIZES = "sizes" - const val PARAM_MODEL_INFO = "modelInfo" + const val PARAM_MODEL_INFO = "model_info" const val PARAM_GENDER = "gender" const val PARAM_STYLE = "style" @@ -177,5 +217,16 @@ internal data class BodyProfileRecommendedSizeParams( const val PARAM_BODY_MEASUREMENT_PREDICTED = "predicted" const val PARAM_BODY_BUST = "bust" const val PARAM_BODY_CHEST = "chest" + + const val APP_ORIGIN_ANDROID = 2 + } + + /** + * Converts a camelCase or PascalCase string to snake_case + */ + private fun toSnakeCase(input: String): String { + return input.replace(Regex("([a-z])([A-Z])"), "$1_$2") + .replace(Regex("([A-Z])([A-Z][a-z])"), "$1_$2") + .lowercase() } } diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/UserBodyProfileJsonParser.kt b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/UserBodyProfileJsonParser.kt index 048737e9..9a0a7273 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/UserBodyProfileJsonParser.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/UserBodyProfileJsonParser.kt @@ -11,13 +11,17 @@ class UserBodyProfileJsonParser : VirtusizeJsonParser { val height = json.optInt(FIELD_HEIGHT) val weight = json.optString(FIELD_WEIGHT) var bodyData = setOf() + var footwearData = mapOf() json.optJSONObject(FIELD_BODY_DATA)?.let { bodyDataJsonObject -> bodyData = JsonUtils.jsonObjectToMeasurements(bodyDataJsonObject) } + json.optJSONObject(FIELD_FOOTWEAR_DATA)?.let { footwearDataJsonObject -> + footwearData = JsonUtils.jsonObjectToMap(footwearDataJsonObject) + } if (age == 0 || height == 0 || weight.isBlank() || bodyData.isEmpty()) { return null } - return UserBodyProfile(gender, age, height, weight, bodyData) + return UserBodyProfile(gender, age, height, weight, bodyData, footwearData) } companion object { @@ -26,5 +30,6 @@ class UserBodyProfileJsonParser : VirtusizeJsonParser { private const val FIELD_HEIGHT = "height" private const val FIELD_WEIGHT = "weight" private const val FIELD_BODY_DATA = "bodyData" + private const val FIELD_FOOTWEAR_DATA = "footwearData" } } diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/Product.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/Product.kt index 04a6cc23..981614ad 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/Product.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/Product.kt @@ -125,4 +125,11 @@ data class Product( fun isAccessory(): Boolean { return productType == 18 || productType == 19 || productType == 25 || productType == 26 } + + /** + * Checks if the product is a shoe + */ + fun isShoe(): Boolean { + return productType == 17 + } } diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/UserBodyProfile.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/UserBodyProfile.kt index 79105a6e..d7121550 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/UserBodyProfile.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/UserBodyProfile.kt @@ -14,4 +14,5 @@ data class UserBodyProfile( val height: Int, val weight: String, val bodyData: Set, + val footwearData: Map, ) diff --git a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApi.kt b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApi.kt index 14a86ba4..8f0c50c7 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApi.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApi.kt @@ -414,7 +414,7 @@ object VirtusizeApi { * @param userBodyProfile [UserBodyProfile] * @see ApiRequest */ - fun getSize( + fun getItemSizeRecommendationRequest( productTypes: List, storeProduct: Product, userBodyProfile: UserBodyProfile, @@ -428,4 +428,26 @@ object VirtusizeApi { .toString() return ApiRequest(url, HttpMethod.POST, bodyProfileRecommendedSizeParams.paramsToMap()) } + + /** + * Gets a API request for getting the recommended shoe size based on the user's body profile + * @param productTypes the list of available [ProductType] + * @param storeProduct [Product] + * @param userBodyProfile [UserBodyProfile] + * @see ApiRequest + */ + fun getShoeSizeRecommendationRequest( + productTypes: List, + storeProduct: Product, + userBodyProfile: UserBodyProfile, + ): ApiRequest { + val bodyProfileRecommendedSizeParams = + BodyProfileRecommendedSizeParams(productTypes, storeProduct, userBodyProfile) + val url = + Uri.parse("${environment.sizeRecommendationApiBaseUrl()}${VirtusizeEndpoint.GetShoeSize.path}") + .buildUpon() + .build() + .toString() + return ApiRequest(url, HttpMethod.POST, bodyProfileRecommendedSizeParams.paramsToMapShoe()) + } } diff --git a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeEndpoint.kt b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeEndpoint.kt index c0c3a6ca..144f89f9 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeEndpoint.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeEndpoint.kt @@ -14,6 +14,10 @@ sealed interface VirtusizeEndpoint { override val path: String = "/item" } + data object GetShoeSize : VirtusizeEndpoint { + override val path: String = "/shoe" + } + data object LatestAoyamaVersion : VirtusizeEndpoint { override val path: String = "/a/aoyama/latest.txt" } diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParamsTests.kt b/virtusize-core/src/test/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParamsTests.kt index 91671efa..7218d2b4 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParamsTests.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/local/BodyProfileRecommendedSizeParamsTests.kt @@ -43,15 +43,15 @@ internal class BodyProfileRecommendedSizeParamsTests { "height": 750 } }, - "modelInfo": { + "gender": "female", + "brand": "Virtusize", + "model_info": { "waist": 56, "bust": 78, "size": "38", "hip": 85, "height": 165 - }, - "gender": "female", - "brand": "Virtusize" + } } """.trimIndent().replace("\\s+|[\\n]+".toRegex(), ""), ) @@ -78,9 +78,9 @@ internal class BodyProfileRecommendedSizeParamsTests { "fit": "regular", "style": "fashionable", "sizes": {}, - "modelInfo": null, - "gender": "female", - "brand": "" + "gender": "male", + "brand": "", + "model_info": null } """.trimIndent().replace("\\s+|[\\n]+".toRegex(), ""), ) @@ -98,94 +98,94 @@ internal class BodyProfileRecommendedSizeParamsTests { assertThat(JSONObject(bodyDataParams).toString()).isEqualTo( """ { - "waistWidth": { - "value": 225, - "predicted": true - }, - "chest": { - "value": 755, - "predicted": true - }, - "bustWidth": { - "value": 245, - "predicted": true - }, - "thigh": { - "value": 480, - "predicted": true - }, - "shoulderWidth": { - "value": 340, - "predicted": true - }, - "hipHeight": { - "value": 750, - "predicted": true - }, - "kneeHeight": { - "value": 395, - "predicted": true - }, - "neck": { - "value": 300, - "predicted": true - }, - "waistHeight": { - "value": 920, - "predicted": true - }, - "hip": { - "value": 830, - "predicted": true - }, - "armpitHeight": { - "value": 1130, - "predicted": true - }, - "bicep": { - "value": 220, - "predicted": true - }, - "inseam": { - "value": 700, - "predicted": true - }, - "headHeight": { - "value": 215, - "predicted": true - }, - "hipWidth": { - "value": 300, - "predicted": true - }, - "sleeve": { - "value": 720, - "predicted": true - }, - "bust": { - "value": 755, - "predicted": true - }, - "waist": { - "value": 630, - "predicted": true - }, - "sleeveLength": { - "value": 520, - "predicted": true - }, - "rise": { - "value": 215, - "predicted": true - }, - "shoulder": { - "value": 370, - "predicted": true - }, - "shoulderHeight": { - "value": 1240, - "predicted": true - } + "armpit_height": { + "value": 1130, + "predicted": true + }, + "shoulder_height": { + "value": 1240, + "predicted": true + }, + "chest": { + "value": 755, + "predicted": true + }, + "bust_width": { + "value": 245, + "predicted": true + }, + "hip_height": { + "value": 750, + "predicted": true + }, + "thigh": { + "value": 480, + "predicted": true + }, + "knee_height": { + "value": 395, + "predicted": true + }, + "head_height": { + "value": 215, + "predicted": true + }, + "hip_width": { + "value": 300, + "predicted": true + }, + "neck": { + "value": 300, + "predicted": true + }, + "hip": { + "value": 830, + "predicted": true + }, + "waist_width": { + "value": 225, + "predicted": true + }, + "waist_height": { + "value": 920, + "predicted": true + }, + "shoulder_width": { + "value": 340, + "predicted": true + }, + "bicep": { + "value": 220, + "predicted": true + }, + "inseam": { + "value": 700, + "predicted": true + }, + "sleeve": { + "value": 720, + "predicted": true + }, + "bust": { + "value": 755, + "predicted": true + }, + "waist": { + "value": 630, + "predicted": true + }, + "rise": { + "value": 215, + "predicted": true + }, + "shoulder": { + "value": 370, + "predicted": true + }, + "sleeve_length": { + "value": 520, + "predicted": true + } } """.trimIndent().replace("\\s+|[\\n]+".toRegex(), ""), ) @@ -227,12 +227,12 @@ internal class BodyProfileRecommendedSizeParamsTests { TestFixtures.userBodyProfile, ) val bodyProfileRecommendedSizeParamsMap = bodyProfileRecommendedSizeParams.paramsToMap() - assertThat(bodyProfileRecommendedSizeParamsMap["userGender"]).isEqualTo("female") - assertThat(bodyProfileRecommendedSizeParamsMap["userWeight"]).isEqualTo(50) - assertThat(bodyProfileRecommendedSizeParamsMap["userHeight"]).isEqualTo(1630) - assertThat(bodyProfileRecommendedSizeParamsMap["bodyData"]).isEqualTo( + assertThat(bodyProfileRecommendedSizeParamsMap["user_gender"]).isEqualTo("female") + assertThat(bodyProfileRecommendedSizeParamsMap["user_weight"]).isEqualTo(50) + assertThat(bodyProfileRecommendedSizeParamsMap["user_height"]).isEqualTo(1630) + assertThat(bodyProfileRecommendedSizeParamsMap["body_data"]).isEqualTo( mutableMapOf( - "waistWidth" to + "waist_width" to mutableMapOf( "value" to 225, "predicted" to true, @@ -242,7 +242,7 @@ internal class BodyProfileRecommendedSizeParamsTests { "value" to 755, "predicted" to true, ), - "bustWidth" to + "bust_width" to mutableMapOf( "value" to 245, "predicted" to true, @@ -252,17 +252,17 @@ internal class BodyProfileRecommendedSizeParamsTests { "value" to 480, "predicted" to true, ), - "shoulderWidth" to + "shoulder_width" to mutableMapOf( "value" to 340, "predicted" to true, ), - "hipHeight" to + "hip_height" to mutableMapOf( "value" to 750, "predicted" to true, ), - "kneeHeight" to + "knee_height" to mutableMapOf( "value" to 395, "predicted" to true, @@ -272,7 +272,7 @@ internal class BodyProfileRecommendedSizeParamsTests { "value" to 300, "predicted" to true, ), - "waistHeight" to + "waist_height" to mutableMapOf( "value" to 920, "predicted" to true, @@ -282,7 +282,7 @@ internal class BodyProfileRecommendedSizeParamsTests { "value" to 830, "predicted" to true, ), - "armpitHeight" to + "armpit_height" to mutableMapOf( "value" to 1130, "predicted" to true, @@ -297,12 +297,12 @@ internal class BodyProfileRecommendedSizeParamsTests { "value" to 700, "predicted" to true, ), - "headHeight" to + "head_height" to mutableMapOf( "value" to 215, "predicted" to true, ), - "hipWidth" to + "hip_width" to mutableMapOf( "value" to 300, "predicted" to true, @@ -322,7 +322,7 @@ internal class BodyProfileRecommendedSizeParamsTests { "value" to 630, "predicted" to true, ), - "sleeveLength" to + "sleeve_length" to mutableMapOf( "value" to 520, "predicted" to true, @@ -337,7 +337,7 @@ internal class BodyProfileRecommendedSizeParamsTests { "value" to 370, "predicted" to true, ), - "shoulderHeight" to + "shoulder_height" to mutableMapOf( "value" to 1240, "predicted" to true, @@ -346,9 +346,9 @@ internal class BodyProfileRecommendedSizeParamsTests { ) val items = bodyProfileRecommendedSizeParamsMap["items"] as Array> for (item in items) { - assertThat(item["extProductId"]).isEqualTo("694") - assertThat(item["productType"]).isEqualTo("jacket") - assertThat(item["itemSizesOrig"]).isEqualTo( + assertThat(item["ext_product_id"]).isEqualTo("694") + assertThat(item["product_type"]).isEqualTo("jacket") + assertThat(item["item_sizes_orig"]).isEqualTo( mutableMapOf( "38" to mutableMapOf( @@ -364,7 +364,7 @@ internal class BodyProfileRecommendedSizeParamsTests { ), ), ) - assertThat(item["additionalInfo"]).isEqualTo( + assertThat(item["additional_info"]).isEqualTo( mutableMapOf( "fit" to "regular", "sizes" to @@ -384,7 +384,7 @@ internal class BodyProfileRecommendedSizeParamsTests { ), "gender" to "female", "brand" to "Virtusize", - "modelInfo" to + "model_info" to mutableMapOf( "waist" to 56, "bust" to 78, diff --git a/virtusize-core/src/test/java/com/virtusize/android/fixtures/TestFixtures.kt b/virtusize-core/src/test/java/com/virtusize/android/fixtures/TestFixtures.kt index 6adc419d..81afb03f 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/fixtures/TestFixtures.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/fixtures/TestFixtures.kt @@ -91,6 +91,13 @@ internal object TestFixtures { "sleeveLength": 520, "shoulderWidth": 340, "shoulderHeight": 1240 + }, + "footwearData": { + "toeShape":"greek", + "size":"30.5", + "type":"sneakers", + "brand":"Virtusize", + "footWidth":"regular" } } """.trimIndent(), @@ -141,5 +148,12 @@ internal object TestFixtures { Measurement("shoulderWidth", 340), Measurement("shoulderHeight", 1240), ), + mapOf( + "toeShape" to "greek", + "size" to "30.5", + "type" to "sneakers", + "brand" to "Virtusize", + "footWidth" to "regular", + ), ) } diff --git a/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTaskTest.kt b/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTaskTest.kt index 096bbb99..99cfd141 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTaskTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTaskTest.kt @@ -81,6 +81,15 @@ internal class VirtusizeApiTaskTest { Measurement("shoulderHeight", 1240), ), ) + assertThat(actualUserBodyProfile?.footwearData).isEqualTo( + mapOf( + "toeShape" to "greek", + "size" to "30.5", + "type" to "sneakers", + "brand" to "Virtusize", + "footWidth" to "regular", + ), + ) } @Test diff --git a/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTest.kt b/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTest.kt index f06cb2a4..5b587a79 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/network/VirtusizeApiTest.kt @@ -288,9 +288,9 @@ internal class VirtusizeApiTest { } @Test - fun `test getSize should return expected API request`() { + fun `test getItemSizeRecommendationRequest should return expected API request`() { val actualApiRequest = - VirtusizeApi.getSize( + VirtusizeApi.getItemSizeRecommendationRequest( ProductFixtures.productTypes(), ProductFixtures.storeProduct(), TestFixtures.userBodyProfile, @@ -301,12 +301,12 @@ internal class VirtusizeApiTest { assertThat(actualApiRequest.url).isEqualTo(expectedUrl) assertThat(actualApiRequest.method).isEqualTo(HttpMethod.POST) assertThat(actualApiRequest.authorization).isEqualTo(false) - assertThat(actualApiRequest.params["userGender"]).isEqualTo("female") - assertThat(actualApiRequest.params["userHeight"]).isEqualTo(1630) - assertThat(actualApiRequest.params["userWeight"]).isEqualTo(50) - assertThat(actualApiRequest.params["bodyData"]).isEqualTo( + assertThat(actualApiRequest.params["user_gender"]).isEqualTo("female") + assertThat(actualApiRequest.params["user_height"]).isEqualTo(1630) + assertThat(actualApiRequest.params["user_weight"]).isEqualTo(50) + assertThat(actualApiRequest.params["body_data"]).isEqualTo( mutableMapOf( - "waistWidth" to + "waist_width" to mutableMapOf( "value" to 225, "predicted" to true, @@ -316,7 +316,7 @@ internal class VirtusizeApiTest { "value" to 755, "predicted" to true, ), - "bustWidth" to + "bust_width" to mutableMapOf( "value" to 245, "predicted" to true, @@ -326,17 +326,17 @@ internal class VirtusizeApiTest { "value" to 480, "predicted" to true, ), - "shoulderWidth" to + "shoulder_width" to mutableMapOf( "value" to 340, "predicted" to true, ), - "hipHeight" to + "hip_height" to mutableMapOf( "value" to 750, "predicted" to true, ), - "kneeHeight" to + "knee_height" to mutableMapOf( "value" to 395, "predicted" to true, @@ -346,7 +346,7 @@ internal class VirtusizeApiTest { "value" to 300, "predicted" to true, ), - "waistHeight" to + "waist_height" to mutableMapOf( "value" to 920, "predicted" to true, @@ -356,7 +356,7 @@ internal class VirtusizeApiTest { "value" to 830, "predicted" to true, ), - "armpitHeight" to + "armpit_height" to mutableMapOf( "value" to 1130, "predicted" to true, @@ -371,12 +371,12 @@ internal class VirtusizeApiTest { "value" to 700, "predicted" to true, ), - "headHeight" to + "head_height" to mutableMapOf( "value" to 215, "predicted" to true, ), - "hipWidth" to + "hip_width" to mutableMapOf( "value" to 300, "predicted" to true, @@ -396,7 +396,7 @@ internal class VirtusizeApiTest { "value" to 630, "predicted" to true, ), - "sleeveLength" to + "sleeve_length" to mutableMapOf( "value" to 520, "predicted" to true, @@ -411,7 +411,7 @@ internal class VirtusizeApiTest { "value" to 370, "predicted" to true, ), - "shoulderHeight" to + "shoulder_height" to mutableMapOf( "value" to 1240, "predicted" to true, @@ -420,9 +420,9 @@ internal class VirtusizeApiTest { ) val items = actualApiRequest.params["items"] as Array> for (item in items) { - assertThat(item["extProductId"]).isEqualTo("694") - assertThat(item["productType"]).isEqualTo("jacket") - assertThat(item["itemSizesOrig"]).isEqualTo( + assertThat(item["ext_product_id"]).isEqualTo("694") + assertThat(item["product_type"]).isEqualTo("jacket") + assertThat(item["item_sizes_orig"]).isEqualTo( mutableMapOf( "38" to mutableMapOf( @@ -438,7 +438,7 @@ internal class VirtusizeApiTest { ), ), ) - assertThat(item["additionalInfo"]).isEqualTo( + assertThat(item["additional_info"]).isEqualTo( mutableMapOf( "fit" to "regular", "sizes" to @@ -458,7 +458,7 @@ internal class VirtusizeApiTest { ), "gender" to "female", "brand" to "Virtusize", - "modelInfo" to + "model_info" to mutableMapOf( "waist" to 56, "bust" to 78, diff --git a/virtusize/src/main/java/com/virtusize/android/VirtusizeRepository.kt b/virtusize/src/main/java/com/virtusize/android/VirtusizeRepository.kt index d131433f..6b8e149d 100644 --- a/virtusize/src/main/java/com/virtusize/android/VirtusizeRepository.kt +++ b/virtusize/src/main/java/com/virtusize/android/VirtusizeRepository.kt @@ -412,13 +412,23 @@ class VirtusizeRepository internal constructor( } val userBodyProfileResponse = virtusizeAPIService.getUserBodyProfile() if (userBodyProfileResponse.successData != null) { - val bodyProfileRecommendedSizeResponse = - virtusizeAPIService.getBodyProfileRecommendedSize( - productTypes, - storeProduct, - userBodyProfileResponse.successData!!, - ) - return bodyProfileRecommendedSizeResponse.successData?.get(0)?.sizeName + if (storeProduct.isShoe()) { + val bodyProfileRecommendedSizeResponse = + virtusizeAPIService.getBodyProfileRecommendedShoeSize( + productTypes, + storeProduct, + userBodyProfileResponse.successData!!, + ) + return bodyProfileRecommendedSizeResponse.successData?.sizeName + } else { + val bodyProfileRecommendedSizeResponse = + virtusizeAPIService.getBodyProfileRecommendedItemSize( + productTypes, + storeProduct, + userBodyProfileResponse.successData!!, + ) + return bodyProfileRecommendedSizeResponse.successData?.get(0)?.sizeName + } } else if (userBodyProfileResponse.failureData?.code != HttpURLConnection.HTTP_NOT_FOUND) { userBodyProfileResponse.failureData?.let { messageHandler.onError(it) diff --git a/virtusize/src/main/java/com/virtusize/android/data/parsers/BodyProfileRecommendedSizeJsonParser.kt b/virtusize/src/main/java/com/virtusize/android/data/parsers/BodyProfileRecommendedSizeJsonParser.kt index b5ac5c83..b69568bb 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/parsers/BodyProfileRecommendedSizeJsonParser.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/parsers/BodyProfileRecommendedSizeJsonParser.kt @@ -19,7 +19,11 @@ internal class BodyProfileRecommendedSizeJsonParser(private val product: Product val scenario = json.optString(SCENARIO) val secondFitScore = json.optDouble(SECOND_FIT_SCORE, 0.0) val secondSize = json.optString(SECOND_SIZE) - val sizeName = json.optString(SIZE_NAME) + var sizeName = json.optString(SIZE_NAME) + if (sizeName.isBlank()) { + val size = json.optString(SIZE_NAME_SHOE) // Fallback for shoe size + sizeName = size.replace(".", ".") + } val thresholdFitScore = json.optDouble(THRESHOLD_FIT_SCORE, 0.0) val willFit = json.optBoolean(WILL_FIT) val virtualItem = @@ -60,6 +64,7 @@ internal class BodyProfileRecommendedSizeJsonParser(private val product: Product private companion object { const val SIZE_NAME = "sizeName" + const val SIZE_NAME_SHOE = "size" const val EXT_PRODUCT_ID = "extProductId" const val FIT_SCORE = "fitScore" const val FIT_SCORE_DIFFERENCE = "fitScoreDifference" diff --git a/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIService.kt b/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIService.kt index 933c97dd..ea004ee3 100644 --- a/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIService.kt +++ b/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIService.kt @@ -131,18 +131,31 @@ internal interface VirtusizeAPIService { suspend fun getUserBodyProfile(): VirtusizeApiResponse /** - * Gets the API response for retrieving the recommended size based on the user body profile + * Gets the API response for retrieving the recommended item size based on the user body profile * @param productTypes a list of product types * @param storeProduct the store product * @param userBodyProfile the user body profile * @return the [VirtusizeApiResponse] with the a list of [BodyProfileRecommendedSize] */ - suspend fun getBodyProfileRecommendedSize( + suspend fun getBodyProfileRecommendedItemSize( productTypes: List, storeProduct: Product, userBodyProfile: UserBodyProfile, ): VirtusizeApiResponse?> + /** + * Gets the API response for retrieving the recommended shoe size based on the user body profile + * @param productTypes a list of product types + * @param storeProduct the store product + * @param userBodyProfile the user body profile + * @return the [VirtusizeApiResponse] with the a list of [BodyProfileRecommendedSize] + */ + suspend fun getBodyProfileRecommendedShoeSize( + productTypes: List, + storeProduct: Product, + userBodyProfile: UserBodyProfile, + ): VirtusizeApiResponse + /** * Loads an image URL and returns the bitmap of the image * @param urlString the image URL string diff --git a/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIServiceImpl.kt b/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIServiceImpl.kt index 78c18582..737148ca 100644 --- a/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIServiceImpl.kt +++ b/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIServiceImpl.kt @@ -256,13 +256,29 @@ internal class VirtusizeAPIServiceImpl( .execute(apiRequest) } - override suspend fun getBodyProfileRecommendedSize( + override suspend fun getBodyProfileRecommendedItemSize( productTypes: List, storeProduct: Product, userBodyProfile: UserBodyProfile, ): VirtusizeApiResponse?> = withContext(Dispatchers.IO) { - val apiRequest = VirtusizeApi.getSize(productTypes, storeProduct, userBodyProfile) + val apiRequest = VirtusizeApi.getItemSizeRecommendationRequest(productTypes, storeProduct, userBodyProfile) + VirtusizeApiTask( + httpURLConnection, + sharedPreferencesHelper, + messageHandler, + ) + .setJsonParser(BodyProfileRecommendedSizeJsonParser(storeProduct)) + .execute(apiRequest) + } + + override suspend fun getBodyProfileRecommendedShoeSize( + productTypes: List, + storeProduct: Product, + userBodyProfile: UserBodyProfile, + ): VirtusizeApiResponse = + withContext(Dispatchers.IO) { + val apiRequest = VirtusizeApi.getShoeSizeRecommendationRequest(productTypes, storeProduct, userBodyProfile) VirtusizeApiTask( httpURLConnection, sharedPreferencesHelper, diff --git a/virtusize/src/test/java/com/virtusize/android/fixtures/TestFixtures.kt b/virtusize/src/test/java/com/virtusize/android/fixtures/TestFixtures.kt index fae2f4da..7670d7b5 100644 --- a/virtusize/src/test/java/com/virtusize/android/fixtures/TestFixtures.kt +++ b/virtusize/src/test/java/com/virtusize/android/fixtures/TestFixtures.kt @@ -216,6 +216,13 @@ internal object TestFixtures { "sleeveLength": 520, "shoulderWidth": 340, "shoulderHeight": 1240 + }, + "footwearData": { + "toeShape":"greek", + "size":"30.5", + "type":"sneakers", + "brand":"Virtusize", + "footWidth":"regular" } } """.trimIndent(), @@ -266,5 +273,12 @@ internal object TestFixtures { Measurement("shoulderWidth", 340), Measurement("shoulderHeight", 1240), ), + mapOf( + "toeShape" to "greek", + "size" to "30.5", + "type" to "sneakers", + "brand" to "Virtusize", + "footWidth" to "regular", + ), ) } diff --git a/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceImplTest.kt b/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceImplTest.kt index 6cb16b5c..315819ce 100644 --- a/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceImplTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceImplTest.kt @@ -521,6 +521,15 @@ class VirtusizeAPIServiceImplTest { Measurement("shoulderHeight", 1240), ), ) + assertThat(actualUserBodyProfile?.footwearData).isEqualTo( + mapOf( + "toeShape" to "greek", + "size" to "30.5", + "type" to "sneakers", + "brand" to "Virtusize", + "footWidth" to "regular", + ), + ) } @Test @@ -606,7 +615,7 @@ class VirtusizeAPIServiceImplTest { ) val actualBodyProfileRecommendedSize = - virtusizeAPIService.getBodyProfileRecommendedSize( + virtusizeAPIService.getBodyProfileRecommendedItemSize( ProductFixtures.productTypes(), ProductFixtures.storeProduct(), TestFixtures.userBodyProfile, @@ -630,7 +639,7 @@ class VirtusizeAPIServiceImplTest { ) val actualApiResponse = - virtusizeAPIService.getBodyProfileRecommendedSize( + virtusizeAPIService.getBodyProfileRecommendedItemSize( ProductFixtures.productTypes(), ProductFixtures.storeProduct(18), TestFixtures.userBodyProfile, diff --git a/virtusize/src/test/java/com/virtusize/android/repository/MockVirtusizeApiService.kt b/virtusize/src/test/java/com/virtusize/android/repository/MockVirtusizeApiService.kt index e98482fa..8bbfb734 100644 --- a/virtusize/src/test/java/com/virtusize/android/repository/MockVirtusizeApiService.kt +++ b/virtusize/src/test/java/com/virtusize/android/repository/MockVirtusizeApiService.kt @@ -85,7 +85,7 @@ internal class MockVirtusizeApiService : VirtusizeAPIService { TODO("Not yet implemented") } - override suspend fun getBodyProfileRecommendedSize( + override suspend fun getBodyProfileRecommendedItemSize( productTypes: List, storeProduct: Product, userBodyProfile: UserBodyProfile, @@ -93,6 +93,14 @@ internal class MockVirtusizeApiService : VirtusizeAPIService { TODO("Not yet implemented") } + override suspend fun getBodyProfileRecommendedShoeSize( + productTypes: List, + storeProduct: Product, + userBodyProfile: UserBodyProfile, + ): VirtusizeApiResponse { + TODO("Not yet implemented") + } + override suspend fun loadImage(urlString: String): Bitmap? { TODO("Not yet implemented") }