From 25603af19fee924db48d39e312f3f5902d40444b Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sat, 3 Aug 2024 16:25:58 +0900 Subject: [PATCH 01/19] Migrate settings.gradle to settings.gradle.kts --- settings.gradle | 1 - settings.gradle.kts | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 settings.gradle create mode 100644 settings.gradle.kts diff --git a/settings.gradle b/settings.gradle deleted file mode 100644 index b9fa71c9..00000000 --- a/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -include ':virtusize', ':virtusize-core', ':sampleAppKotlin', ':sampleappjava' diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..1fe5e7bc --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,4 @@ +include(":virtusize") +include(":virtusize-core") +include(":sampleAppKotlin") +include(":sampleappjava") \ No newline at end of file From 03a2db5e5fb741a1aa73978a6718e7a2365d3966 Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sat, 3 Aug 2024 16:26:13 +0900 Subject: [PATCH 02/19] Delete virtusize.aar --- virtusize.aar | Bin 100028 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 virtusize.aar diff --git a/virtusize.aar b/virtusize.aar deleted file mode 100644 index 09384ca03147f67bd7e99551ecd693e80403f47e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 100028 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

i#_!6Sf|kG}_ZMmgc#gXit}$J_Ua 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 Date: Sat, 3 Aug 2024 21:39:34 +0900 Subject: [PATCH 03/19] Format the code --- .../java/com/virtusize/sampleappkotlin/App.kt | 41 +- .../virtusize/sampleappkotlin/MainActivity.kt | 90 +- .../sampleappkotlin/ProductActivity.kt | 46 +- .../sampleappkotlin/WebViewFragment.kt | 65 +- .../drawable-v24/ic_launcher_foreground.xml | 46 +- .../res/drawable/ic_launcher_background.xml | 240 ++-- .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 +- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2963 -> 0 bytes .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4905 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2060 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2783 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4490 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 6895 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6387 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10413 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 9128 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 15132 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes .../src/main/res/values/colors.xml | 2 +- .../src/main/res/values/themes.xml | 11 + .../src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 + sampleappjava/.gitignore | 2 +- sampleappjava/proguard-rules.pro | 2 +- .../ExampleInstrumentedTest.java | 26 - sampleappjava/src/main/AndroidManifest.xml | 14 +- .../java/com/virtusize/sampleappjava/App.java | 1 + .../drawable-v24/ic_launcher_foreground.xml | 46 +- .../res/drawable/ic_launcher_background.xml | 240 ++-- .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 +- .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 +- .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 2963 -> 0 bytes .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 1404 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 4905 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 2898 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2060 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 982 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 2783 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 1772 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4490 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 1900 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 6895 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 3918 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6387 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 2884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 10413 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 5914 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 9128 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 3844 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 15132 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 7778 bytes sampleappjava/src/main/res/values/strings.xml | 2 +- sampleappjava/src/main/res/values/styles.xml | 11 - .../src/main/res/values/themes.xml | 0 .../sampleappjava/ExampleUnitTest.java | 17 - .../android/SharedPreferencesHelper.kt | 10 +- .../local/BodyProfileRecommendedSizeParams.kt | 83 +- .../local/SizeComparisonRecommendedSize.kt | 2 +- .../data/local/VirtusizeEnvironment.kt | 8 +- .../android/data/local/VirtusizeError.kt | 2 +- .../android/data/local/VirtusizeErrorType.kt | 7 +- .../android/data/local/VirtusizeEvent.kt | 6 +- .../android/data/local/VirtusizeLanguage.kt | 2 +- .../data/local/VirtusizeMessageHandler.kt | 6 +- .../android/data/local/VirtusizeOrder.kt | 121 +- .../android/data/local/VirtusizeOrderItem.kt | 167 +-- .../android/data/local/VirtusizeProduct.kt | 12 +- .../android/data/local/VirtusizeRegion.kt | 2 +- .../android/data/parsers/DataJsonParser.kt | 9 +- .../android/data/parsers/JsonUtils.kt | 44 +- .../data/parsers/ProductCheckJsonParser.kt | 7 +- .../android/data/parsers/ProductJsonParser.kt | 10 +- .../data/parsers/ProductTypeJsonParser.kt | 7 +- .../data/parsers/UserSessionInfoJsonParser.kt | 1 - .../android/data/remote/BrandSizing.kt | 2 +- .../com/virtusize/android/data/remote/Data.kt | 2 +- .../android/data/remote/I18nLocalization.kt | 20 +- .../android/data/remote/Measurement.kt | 2 +- .../virtusize/android/data/remote/Product.kt | 37 +- .../android/data/remote/ProductCheck.kt | 2 +- .../android/data/remote/ProductSize.kt | 2 +- .../android/data/remote/ProductType.kt | 2 +- .../data/remote/StoreProductAdditionalInfo.kt | 2 +- .../android/data/remote/StoreProductMeta.kt | 2 +- .../android/data/remote/UserBodyProfile.kt | 2 +- .../android/data/remote/UserSessionInfo.kt | 2 +- .../virtusize/android/data/remote/Weight.kt | 2 +- .../virtusize/android/network/VirtusizeApi.kt | 156 +-- .../android/network/VirtusizeApiResponse.kt | 1 + .../android/network/VirtusizeApiTask.kt | 214 ++-- .../android/network/VirtusizeEndpoint.kt | 11 +- .../com/virtusize/android/util/Extensions.kt | 45 +- .../BodyProfileRecommendedSizeParamsTests.kt | 422 ++++--- .../data/local/VirtusizeOrderItemTest.kt | 127 +- .../android/data/local/VirtusizeOrderTest.kt | 65 +- .../data/parsers/DataJsonParserTest.kt | 22 +- .../android/data/parsers/JsonUtilsTest.kt | 215 ++-- .../parsers/ProductCheckJsonParserTest.kt | 35 +- .../data/parsers/ProductTypeJsonParserTest.kt | 22 +- .../parsers/UserBodyProfileJsonParserTest.kt | 1 - .../parsers/UserProductsJsonParserTest.kt | 42 +- .../android/data/remote/ProductTest.kt | 173 +-- .../android/fixtures/ProductFixtures.kt | 241 ++-- .../android/fixtures/TestFixtures.kt | 178 +-- .../android/network/VirtusizeApiTaskTest.kt | 233 ++-- .../android/network/VirtusizeApiTest.kt | 412 +++--- virtusize/.gitignore | 2 +- virtusize/proguard-rules.pro | 2 +- .../com/virtusize/android/CallbackHandler.kt | 6 +- .../java/com/virtusize/android/Virtusize.kt | 317 ++--- .../com/virtusize/android/VirtusizeBuilder.kt | 27 +- .../android/VirtusizeFitIllustratorWebView.kt | 786 ++++++------ .../virtusize/android/VirtusizePresenter.kt | 9 +- .../virtusize/android/VirtusizeRepository.kt | 90 +- .../data/local/ProductComparisonFitInfo.kt | 2 +- .../data/local/VirtusizeInfoCategory.kt | 2 +- .../android/data/local/VirtusizeParams.kt | 3 +- .../android/data/local/VirtusizeViewStyle.kt | 2 +- .../BodyProfileRecommendedSizeJsonParser.kt | 33 +- .../parsers/I18nLocalizationJsonParser.kt | 155 +-- .../android/data/parsers/StoreJsonParser.kt | 2 +- .../StoreProductAdditionalInfoJsonParser.kt | 21 +- .../data/parsers/UserAuthDataJsonParser.kt | 1 - .../data/remote/BodyProfileRecommendedSize.kt | 2 +- .../remote/BodyProfileRecommendedSizeNew.kt | 2 +- .../data/remote/ProductMetaDataHints.kt | 2 +- .../virtusize/android/data/remote/Store.kt | 2 +- .../android/data/remote/UserAuthData.kt | 2 +- .../android/data/remote/VirtualItem.kt | 2 +- .../android/data/remote/WillFitForSizes.kt | 2 +- .../flutter/VirtusizeFlutterRepository.kt | 39 +- .../android/flutter/VirtusizeFlutterUtils.kt | 40 +- .../android/network/VirtusizeAPIService.kt | 303 ++--- .../android/ui/DotsLoadingTextView.kt | 24 +- .../virtusize/android/ui/VirtusizeButton.kt | 227 ++-- .../ui/VirtusizeFitIllustratorFragment.kt | 47 +- .../android/ui/VirtusizeInPageMini.kt | 463 +++---- .../android/ui/VirtusizeInPageStandard.kt | 1119 +++++++++-------- .../ui/VirtusizeInPageStandardViewModel.kt | 3 +- .../android/ui/VirtusizeInPageView.kt | 59 +- .../android/ui/VirtusizeProductImageView.kt | 72 +- .../com/virtusize/android/ui/VirtusizeView.kt | 12 +- .../android/ui/VirtusizeWebViewFragment.kt | 146 ++- .../com/virtusize/android/util/FontUtils.kt | 13 +- .../virtusize/android/util/VirtusizeUtils.kt | 32 +- .../data/parsers/StoreJsonParserTest.kt | 51 - .../android/fixtures/ProductFixtures.kt | 313 ++--- .../android/fixtures/TestFixtures.kt | 258 ++-- .../android/network/MockHttpsURLConnection.kt | 55 +- .../network/VirtusizeAPIServiceTest.kt | 657 +++++----- .../parsers/BrandSizingJsonParserTest.kt | 19 +- .../parsers/I18nLocalizationJsonParserTest.kt | 82 +- .../ProductMetaDataHintsJsonParserTest.kt | 14 +- .../parsers/ProductSizeJsonParserTest.kt | 49 +- .../android/parsers/StoreJsonParserTest.kt | 52 + ...toreProductAdditionalInfoJsonParserTest.kt | 21 +- .../parsers/StoreProductJsonParserTest.kt | 41 +- .../parsers/StoreProductMetaJsonParserTest.kt | 30 +- .../android/util/ExtensionsKtTest.kt | 1 - .../android/util/VirtusizeUtilsTest.kt | 227 ++-- 170 files changed, 5400 insertions(+), 4697 deletions(-) delete mode 100644 sampleAppKotlin/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 sampleAppKotlin/src/main/res/mipmap-hdpi/ic_launcher.webp delete mode 100644 sampleAppKotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 sampleAppKotlin/src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 sampleAppKotlin/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 sampleAppKotlin/src/main/res/mipmap-mdpi/ic_launcher.webp delete mode 100644 sampleAppKotlin/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 sampleAppKotlin/src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher.webp delete mode 100644 sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 sampleAppKotlin/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 sampleAppKotlin/src/main/res/mipmap-xxhdpi/ic_launcher.webp delete mode 100644 sampleAppKotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 sampleAppKotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 sampleAppKotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 sampleAppKotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.webp delete mode 100644 sampleAppKotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 sampleAppKotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 sampleAppKotlin/src/main/res/values/themes.xml create mode 100644 sampleAppKotlin/src/main/res/xml/backup_rules.xml create mode 100644 sampleAppKotlin/src/main/res/xml/data_extraction_rules.xml delete mode 100644 sampleappjava/src/androidTest/java/com/virtusize/sampleappjava/ExampleInstrumentedTest.java delete mode 100644 sampleappjava/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 sampleappjava/src/main/res/mipmap-hdpi/ic_launcher.webp delete mode 100644 sampleappjava/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 sampleappjava/src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 sampleappjava/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 sampleappjava/src/main/res/mipmap-mdpi/ic_launcher.webp delete mode 100644 sampleappjava/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 sampleappjava/src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher.webp delete mode 100644 sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 sampleappjava/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 sampleappjava/src/main/res/mipmap-xxhdpi/ic_launcher.webp delete mode 100644 sampleappjava/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 sampleappjava/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 sampleappjava/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 sampleappjava/src/main/res/mipmap-xxxhdpi/ic_launcher.webp delete mode 100644 sampleappjava/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 sampleappjava/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp delete mode 100644 sampleappjava/src/main/res/values/styles.xml rename sampleAppKotlin/src/main/res/values/styles.xml => sampleappjava/src/main/res/values/themes.xml (100%) delete mode 100644 sampleappjava/src/test/java/com/virtusize/sampleappjava/ExampleUnitTest.java delete mode 100644 virtusize/src/test/java/com/virtusize/android/data/parsers/StoreJsonParserTest.kt rename virtusize/src/test/java/com/virtusize/android/{data => }/parsers/BrandSizingJsonParserTest.kt (77%) rename virtusize/src/test/java/com/virtusize/android/{data => }/parsers/I18nLocalizationJsonParserTest.kt (78%) rename virtusize/src/test/java/com/virtusize/android/{data => }/parsers/ProductMetaDataHintsJsonParserTest.kt (67%) rename virtusize/src/test/java/com/virtusize/android/{data => }/parsers/ProductSizeJsonParserTest.kt (68%) create mode 100644 virtusize/src/test/java/com/virtusize/android/parsers/StoreJsonParserTest.kt rename virtusize/src/test/java/com/virtusize/android/{data => }/parsers/StoreProductAdditionalInfoJsonParserTest.kt (83%) rename virtusize/src/test/java/com/virtusize/android/{data => }/parsers/StoreProductJsonParserTest.kt (86%) rename virtusize/src/test/java/com/virtusize/android/{data => }/parsers/StoreProductMetaJsonParserTest.kt (91%) diff --git a/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/App.kt b/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/App.kt index 92c513ac..e94cd8e4 100644 --- a/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/App.kt +++ b/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/App.kt @@ -7,28 +7,29 @@ import com.virtusize.android.data.local.VirtusizeEnvironment import com.virtusize.android.data.local.VirtusizeInfoCategory import com.virtusize.android.data.local.VirtusizeLanguage -class App: Application() { - lateinit var Virtusize: Virtusize +class App : Application() { + lateinit var virtusize: Virtusize override fun onCreate() { super.onCreate() - // Initialize Virtusize instance for your application - Virtusize = VirtusizeBuilder().init(this) - // Only the API key is required - .setApiKey("15cc36e1d7dad62b8e11722ce1a245cb6c5e6692") - // For using the Order API, a user ID is required - .setUserId("123") - // By default, the Virtusize environment will be set to GLOBAL - .setEnv(VirtusizeEnvironment.JAPAN) - // By default, the initial language will be set based on the Virtusize environment - .setLanguage(VirtusizeLanguage.EN) - // By default, ShowSGI is false - .setShowSGI(true) - // By default, Virtusize allows all the possible languages - .setAllowedLanguages(mutableListOf(VirtusizeLanguage.EN, VirtusizeLanguage.JP)) - // By default, Virtusize displays all the possible info categories in the Product Details tab - .setDetailsPanelCards(mutableListOf(VirtusizeInfoCategory.BRAND_SIZING, VirtusizeInfoCategory.GENERAL_FIT)) - .build() + // Initialize Virtusize instance for your application + virtusize = + VirtusizeBuilder().init(this) + // Only the API key is required + .setApiKey("15cc36e1d7dad62b8e11722ce1a245cb6c5e6692") + // For using the Order API, a user ID is required + .setUserId("123") + // By default, the Virtusize environment will be set to GLOBAL + .setEnv(VirtusizeEnvironment.JAPAN) + // By default, the initial language will be set based on the Virtusize environment + .setLanguage(VirtusizeLanguage.EN) + // By default, ShowSGI is false + .setShowSGI(true) + // By default, Virtusize allows all the possible languages + .setAllowedLanguages(mutableListOf(VirtusizeLanguage.EN, VirtusizeLanguage.JP)) + // By default, Virtusize displays all the possible info categories in the Product Details tab + .setDetailsPanelCards(mutableListOf(VirtusizeInfoCategory.BRAND_SIZING, VirtusizeInfoCategory.GENERAL_FIT)) + .build() } -} \ No newline at end of file +} diff --git a/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/MainActivity.kt b/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/MainActivity.kt index 67108fff..3ccfb4a8 100644 --- a/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/MainActivity.kt +++ b/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/MainActivity.kt @@ -15,7 +15,6 @@ import com.virtusize.android.util.spToPx import com.virtusize.sampleappkotlin.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { - companion object { const val TAG = "MAIN_ACTIVITY" } @@ -29,24 +28,26 @@ class MainActivity : AppCompatActivity() { /* * Register message handler to listen to events from Virtusize */ - (application as App).Virtusize.registerMessageHandler(activityMessageHandler) + (application as App).virtusize.registerMessageHandler(activityMessageHandler) /* * Set up Virtusize product for all the Virtusize views */ - val product = VirtusizeProduct( - externalId = "vs_dress", - imageUrl = "http://www.image.com/goods/12345.jpg" - ) - (application as App).Virtusize.load(product) + val product = + VirtusizeProduct( + externalId = "vs_dress", + imageUrl = "http://www.image.com/goods/12345.jpg", + ) + (application as App).virtusize.load(product) /* * Set up Virtusize button */ + // Virtusize opens automatically when button is clicked - (application as App).Virtusize.setupVirtusizeView( + (application as App).virtusize.setupVirtusizeView( virtusizeView = binding.exampleVirtusizeButton, - product = product + product = product, ) // Set up the Virtusize view style programmatically binding.exampleVirtusizeButton.virtusizeViewStyle = VirtusizeViewStyle.TEAL @@ -54,10 +55,10 @@ class MainActivity : AppCompatActivity() { /* * Set up Virtusize InPage Standard */ - (application as App).Virtusize + (application as App).virtusize .setupVirtusizeView( virtusizeView = binding.exampleVirtusizeInPageStandard, - product = product + product = product, ) binding.exampleVirtusizeInPageStandard.virtusizeViewStyle = VirtusizeViewStyle.TEAL // If you like, you can set up the horizontal margins between the edges of the app screen and the InPage Standard view @@ -77,9 +78,9 @@ class MainActivity : AppCompatActivity() { /* * Set up Virtusize InPage Mini */ - (application as App).Virtusize.setupVirtusizeView( + (application as App).virtusize.setupVirtusizeView( virtusizeView = binding.exampleVirtusizeInPageMini, - product = product + product = product, ) binding.exampleVirtusizeInPageMini.virtusizeViewStyle = VirtusizeViewStyle.TEAL @@ -101,9 +102,7 @@ class MainActivity : AppCompatActivity() { * exampleVirtusizeInPageMini.dismissVirtusizeView() */ - /* - * The sample function to send an order to the Virtusize server - */ + // The sample function to send an order to the Virtusize server sendOrderSample() } @@ -116,25 +115,27 @@ class MainActivity : AppCompatActivity() { */ private fun sendOrderSample() { val order = VirtusizeOrder("888400111032") - order.items = mutableListOf( - VirtusizeOrderItem( - "P001", - "L", - "Large", - "P001_SIZEL_RED", - "http://images.example.com/products/P001/red/image1xl.jpg", - "Red", - "W", - 51000.00, - "JPY", - 1, - "http://example.com/products/P001" + order.items = + mutableListOf( + VirtusizeOrderItem( + "P001", + "L", + "Large", + "P001_SIZEL_RED", + "http://images.example.com/products/P001/red/image1xl.jpg", + "Red", + "W", + 51000.00, + "JPY", + 1, + "http://example.com/products/P001", + ), ) - ) (application as App) - .Virtusize - .sendOrder(order, + .virtusize + .sendOrder( + order, // this optional success callback is called when the app successfully sends the order onSuccess = { Log.i(TAG, "Successfully sent the order") @@ -142,23 +143,28 @@ class MainActivity : AppCompatActivity() { // this optional error callback is called when an error occurs when the app is sending the order onError = { error -> Log.e(TAG, error.message) - }) + }, + ) } override fun onPause() { // Always un register message handler in onPause() or depending on implementation onStop(). (application as App) - .Virtusize.unregisterMessageHandler(activityMessageHandler) + .virtusize.unregisterMessageHandler(activityMessageHandler) super.onPause() } - private val activityMessageHandler = object : VirtusizeMessageHandler { - override fun onEvent(product: VirtusizeProduct, event: VirtusizeEvent) { - Log.i(TAG, event.name) + private val activityMessageHandler = + object : VirtusizeMessageHandler { + override fun onEvent( + product: VirtusizeProduct, + event: VirtusizeEvent, + ) { + Log.i(TAG, event.name) + } + + override fun onError(error: VirtusizeError) { + Log.e(TAG, error.message) + } } - - override fun onError(error: VirtusizeError) { - Log.e(TAG, error.message) - } - } } diff --git a/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/ProductActivity.kt b/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/ProductActivity.kt index 3d0d20f1..bf47eb89 100644 --- a/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/ProductActivity.kt +++ b/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/ProductActivity.kt @@ -8,16 +8,17 @@ import com.virtusize.sampleappkotlin.databinding.ActivityProductBinding class ProductActivity : AppCompatActivity() { companion object { - val externalProductIds = listOf( - "vs_dress", - "vs_top", - "vs_shirt", - "vs_coat", - "vs_jacket", - "vs_sweater", - "vs_skirt", - "vs_pants" - ) + val externalProductIds = + listOf( + "vs_dress", + "vs_top", + "vs_shirt", + "vs_coat", + "vs_jacket", + "vs_sweater", + "vs_skirt", + "vs_pants", + ) } override fun onCreate(savedInstanceState: Bundle?) { @@ -27,31 +28,32 @@ class ProductActivity : AppCompatActivity() { setContentView(binding.root) setContentView(R.layout.activity_product) - val product = VirtusizeProduct( - externalId = externalProductIds.random(), - imageUrl = "http://www.image.com/goods/12345.jpg" - ) + val product = + VirtusizeProduct( + externalId = externalProductIds.random(), + imageUrl = "http://www.image.com/goods/12345.jpg", + ) - (application as App).Virtusize.load(product) + (application as App).virtusize.load(product) - (application as App).Virtusize.setupVirtusizeView( + (application as App).virtusize.setupVirtusizeView( virtusizeView = binding.exampleVirtusizeButton, - product = product + product = product, ) - (application as App).Virtusize + (application as App).virtusize .setupVirtusizeView( virtusizeView = binding.exampleVirtusizeInPageStandard, - product = product + product = product, ) - (application as App).Virtusize.setupVirtusizeView( + (application as App).virtusize.setupVirtusizeView( virtusizeView = binding.exampleVirtusizeInPageMini, - product = product + product = product, ) binding.nextProductButton.setOnClickListener { startActivity(Intent(this, ProductActivity::class.java)) } } -} \ No newline at end of file +} diff --git a/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/WebViewFragment.kt b/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/WebViewFragment.kt index 1e58ec19..0a635cdb 100644 --- a/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/WebViewFragment.kt +++ b/sampleAppKotlin/src/main/java/com/virtusize/sampleappkotlin/WebViewFragment.kt @@ -16,8 +16,7 @@ import com.virtusize.android.auth.VirtusizeAuth import com.virtusize.android.auth.VirtusizeWebView import com.virtusize.android.util.urlString -class WebViewFragment: DialogFragment() { - +class WebViewFragment : DialogFragment() { companion object { private val TAG = WebViewFragment::class.simpleName } @@ -25,11 +24,13 @@ class WebViewFragment: DialogFragment() { private lateinit var webView: VirtusizeWebView // 1. Register for getting the result of the activity - private val virtusizeSNSAuthLauncher = registerForActivityResult( - ActivityResultContracts.StartActivityForResult()) { result -> - // 3. Handle the SNS auth result by passing the webview and the result to the `VirtusizeAuth.handleVirtusizeSNSAuthResult` function - VirtusizeAuth.handleVirtusizeSNSAuthResult(webView, result.resultCode, result.data) - } + private val virtusizeSNSAuthLauncher = + registerForActivityResult( + ActivityResultContracts.StartActivityForResult(), + ) { result -> + // 3. Handle the SNS auth result by passing the webview and the result to the `VirtusizeAuth.handleVirtusizeSNSAuthResult` function + VirtusizeAuth.handleVirtusizeSNSAuthResult(webView, result.resultCode, result.data) + } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) @@ -44,12 +45,15 @@ class WebViewFragment: DialogFragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View? { return inflater.inflate(R.layout.webview_vs, container, false) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) webView = view.findViewById(R.id.webView) @@ -57,14 +61,17 @@ class WebViewFragment: DialogFragment() { webView.setVirtusizeSNSAuthLauncher(virtusizeSNSAuthLauncher) object : WebViewClient() { - override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean { - Log.d(TAG, "shouldOverrideUrlLoading ${url}") + override fun shouldOverrideUrlLoading( + view: WebView?, + url: String, + ): Boolean { + Log.d(TAG, "shouldOverrideUrlLoading $url") return super.shouldOverrideUrlLoading(view, url) } override fun shouldOverrideUrlLoading( view: WebView?, - request: WebResourceRequest? + request: WebResourceRequest?, ): Boolean { request?.let { Log.d(TAG, "shouldOverrideUrlLoading ${request.urlString}") @@ -72,27 +79,31 @@ class WebViewFragment: DialogFragment() { return super.shouldOverrideUrlLoading(view, request) } - override fun onPageFinished(view: WebView?, url: String?) { + override fun onPageFinished( + view: WebView?, + url: String?, + ) { Log.d(TAG, "onPageFinished ${view?.url}") super.onPageFinished(view, url) } }.also { webView.webViewClient = it } - webView.webChromeClient = object : WebChromeClient() { - override fun onCreateWindow( - view: WebView?, - isDialog: Boolean, - isUserGesture: Boolean, - resultMsg: Message? - ): Boolean { - Log.d(TAG, "onCreateWindow ${view?.url}") - return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg) - } + webView.webChromeClient = + object : WebChromeClient() { + override fun onCreateWindow( + view: WebView?, + isDialog: Boolean, + isUserGesture: Boolean, + resultMsg: Message?, + ): Boolean { + Log.d(TAG, "onCreateWindow ${view?.url}") + return super.onCreateWindow(view, isDialog, isUserGesture, resultMsg) + } - override fun onCloseWindow(window: WebView?) { - Log.d(TAG, "onCloseWindow") + override fun onCloseWindow(window: WebView?) { + Log.d(TAG, "onCloseWindow") + } } - } webView.loadUrl("https://demo.virtusize.com/") } @@ -102,4 +113,4 @@ class WebViewFragment: DialogFragment() { webView.stopLoading() webView.destroy() } -} \ No newline at end of file +} diff --git a/sampleAppKotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sampleAppKotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml index 6348baae..2b068d11 100644 --- a/sampleAppKotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/sampleAppKotlin/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -1,34 +1,30 @@ - + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + android:color="#44000000" + android:offset="0.0" /> + android:color="#00000000" + android:offset="1.0" /> - + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" + android:strokeWidth="1" + android:strokeColor="#00000000" /> + \ No newline at end of file diff --git a/sampleAppKotlin/src/main/res/drawable/ic_launcher_background.xml b/sampleAppKotlin/src/main/res/drawable/ic_launcher_background.xml index a0ad202f..07d5da9c 100644 --- a/sampleAppKotlin/src/main/res/drawable/ic_launcher_background.xml +++ b/sampleAppKotlin/src/main/res/drawable/ic_launcher_background.xml @@ -1,74 +1,170 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sampleAppKotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sampleAppKotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index bbd3e021..6f3b755b 100644 --- a/sampleAppKotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/sampleAppKotlin/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,6 @@ - - + + + \ No newline at end of file diff --git a/sampleAppKotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sampleAppKotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index bbd3e021..6f3b755b 100644 --- a/sampleAppKotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/sampleAppKotlin/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,6 @@ - - + + + \ No newline at end of file diff --git a/sampleAppKotlin/src/main/res/mipmap-hdpi/ic_launcher.png b/sampleAppKotlin/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 898f3ed59ac9f3248734a00e5902736c9367d455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2963 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 JPDHLkV1g@XpTGbB diff --git a/sampleAppKotlin/src/main/res/mipmap-hdpi/ic_launcher.webp b/sampleAppKotlin/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3 GIT binary patch literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG literal 0 HcmV?d00001 diff --git a/sampleAppKotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sampleAppKotlin/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index dffca3601eba7bf5f409bdd520820e2eb5122c75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4905 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 literal 0 HcmV?d00001 diff --git a/sampleAppKotlin/src/main/res/mipmap-mdpi/ic_launcher.png b/sampleAppKotlin/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 64ba76f75e9ce021aa3d95c213491f73bcacb597..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2060 zcmV+n2=n)eP)3$g6n-PcZs4>q4bV;KlnN~%kbn}!V8maBKN?~PDN77Zj6xT>KxccMrJYVYoo)adu8>W% zmv*U9KCo@D{=sCEstjFGl{%?R9Bd_S;`C@G{FNG~X;+5Z0h*dJ1r|5g4wB8=?S#Zy zt3sAsXM@aL)nWAyCYz08&uXYp$}38nkeVvA0^C`|ts22ve2Y2>mf~J~_Til&y|FUz z%#l)O^+i>bDr7NsoiC}@GN^5^{=sAkPSF?VF#7ysBZm@DnF?;le_~|Un-B}Itc2u|IlX``0V1M3jKlcCTY73+_+5_^1 zO|_7<%PEyPhbqxCEnFv#uom}FdO$lY%`OKi#h<5Co8ZPBFZA{I!|wAx!c?aisEfxs z?T$*AUTc9D8_Hpt%L37MoudCVml+QIa-Q{X>F$I{4t=051yd2KXJy7g2ho;dPy9%m z&|3%hK)bgG?)N=_y3^l5BAU(HpEX16sc+%jjdr-wd5e*w`^js6LDPj(u<}q7%axih zoQB@MKIp*y%l0*noe!-3>L8Nvz`X|#;P=}%;m-Yg;Pd%Hg6jXkc0~S4=WWP7_Qlvb zG1>9)E0=~O9SWcSdXd@th$;|?3QV+Z@1bR;tdb%M2ko%(GTA+u#e@F7$5Mb+;mB`4 z!xVgv{Jp95%Y!hpT7-)jrQ~&IJFY@h`L?H{0L^~?0CJaZ z{tZjr)sT1m=#VQw^-Fg;S$l@ofMbuY0uykS+-JWJI=h~`ci}FY$50ATJ+%wA zO77DqVS>075^y6_kJfo$5r(}BH#(lkaYNw(n&Hbh&XQd-lYhgIk-UdHhZ4HzOR6cX9O(7$kLq}D}u9EB; z-dhHFDZZ<8Lc2GP(}(AKLrJ-Oau&a1s?6Nk^&FO z6KSRZhEqx_SQs6S0+Eca!Fb^G1gONmI zC+HbyhfVOuc?OI&h7uoNn}=`c_>iW5NO1q-GUX8K1^!Zxzl z4XfveR)GIBSo>}=cI+IH9~|U>#(X~teA-&84{aZTo0BMk;yjBqEL^gX=_9kDnP=}a z`+sm4^17nldnZj&U`51GznG$gf}Fz|OlbvM2~cNtN6bbO;LjW>4doDpXIHr_#-WEK zTp3oTSyarnG|L?64R(Lh#u7IM@+CF;0?j-dAKR%u-gp$bMThf`Y=V%QniZFqb4;b% z+^sU^c~$y+58W}2ds$fqbXadxS)oD}YcBF8+Kmro`dqK7bh9_jZo>N(2|7ZqH?6u% zs@LZQps|*E)s_+u&N{X0R(-hsYauy#KI0bVpUP;&tcc8vw<4D;UKP1mLj0?AU!cHb ztdAKWi}A~qZL?OzGg+1b@q^keUNsrViJ`HuE@E!RO5*b9*&nDxR@U?Q6pMIaj1kMY qJl2nQa+aK&iDQb84*TpHAJ>1BQ$$nT?9A!_0000B%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!T+Hy9+Dw zQlg?UKB$_cZ8RBMYcyI%jkQf{#wz1Xr!PxQ>w~B~cKP~!=iIw{_rdOp7tZhwZ1+g(AXy-HL10DFmbXNx@L~ z3H0wQYEpsnp{iIyzhEeKgc((i$;}oAoqHl}Yb`&gx~}ISy|wl# zwdwQ;nvEgzkAnwYj%g}=Nide26RJwsNTUEE)Q2P-5}7cQ3Z84R%7rdvN4sQKhOlPcRnSrOp+WGP}nNJgfkDx!pMkypKGe90p51ezT#4MxAxQ zN3CC+fuRy0nP8u@+)%h}@FHZ>vWFTTCD?*bPf|6Oz4#LAYDsH*sO<_ z+8Vve2|wE19JrkK!TNc*tzkb>2=OxIfDS8-yiLEA$m0k(kQf0ZJlj+Q&+pg*@-o6x zTdEi#&vL>m?`;jX+>v0bbWnM`S<~tiA>-z6^m&Xo6y=iH&}dMDp40vqOvn?CbR0P3 z0YX_`z8klIalWefMaf}lN@-MvK>)C@OTMQsvEFV1j6zbmglN3)tDNw{&IYft@#yp|U;GYg&z^)Rt7d@u#0Bpe zimnOEmq&Tef~aWH7SjqERa#-iBMX%jZKUfNcy71bp|`IOKD_d0nA~D<-XkQV*jewl zx|K$GjP@M*^t)>e04FWS7-Uwy|!6q{ICob5gfvYaErq&g;Btk^VqnotOu zSN-|V;a*P<^rDbv9KD!YExR|ex)jop)as*$VeKa$K-3I_~rZ#$8n0D;V;;rwan!I2{& zEnl34toAlI^wpPe zlye)Ao4ycY%W~JdLaI0e(MHvF%G1SkH=uyAXf{=!ABS!n#lZ@o8CZ4XFmw8#1n{&R zVs(YP+3GCIkwRjs%TCiYQa(?iP=b^m$jib}=-N*{ggXx&44S-zukU>W+LOO#ZOZ!~ zOnukpUM6x&FsRNVXIChVTfbhB(rD_SHz|4}839cXjAmbiVtspfigR#uEFjIMj@si>Ore+Oei$<1cCarcfF2@0*j682U1A9rp; zlE=d6(}XYz#@Cd03QHCwxdi0=G&$N_{=Yy1XfbK~!v(L-Fa7gxu<_$VaOSVq1CpmY z8$Ujb&-~r%UfZSfpfHyQ7GTlb5>~#R>JqSaSxPVhD7~ea?b-3_j}BnQxCvh0zmvuF zfymQ6C7Oj$o(rpg(e8EsF8b6fI~#$e4S@tKotNPf@Ro97lv&dmNB}MOzKDHx{Td^7 z^e>kK&H&X>w(nxk__|+v<^;uhpfq|w0oCgN2n*&Uy98ur#zdLa9sUH2!{g=78$;%} z1L1P#zaX{-%}ARM>G(3`OF*1abzPV`HC~?1g-^B_&(OXN<=~`T0!1J)ouwb`hnx4h z9=m{>-*my^gYQ9FLp5Z*znzJYxJcY)*bL{8bEG_x3mc;?*yV2q=Kg#a+Xvy`pEue zJ2#<55|A&7Ku(lOR2IUxb#E82l~|riL@t>>J=|1!XP{(Gfq7D*RSSuh3Wmux1H9O5 zbzVzIvg#nSb+dS_bpfB9xub!%!Jvc0T8>$5O?a$?#5xXzQ6&nfaS6~B@Yl=oyt`5J zUi|^Lo>^h?bXpN!k$b{#I*o}Gg+L0KqjiNap+>{bdB$Wh1B{gdNt&z zkU*wl;*p0Tp96`fH`Pew34JvBLf)EFl)AaU3W$CXzIJ5}*_hmnyplOlgkJ%5dN1-^ zfYFOQ7f|g*o(nK@@|F3Nh4!=hOBWWfJjm^}QhYrdl{|g|c5+Shdb>Od$s<#GvjwI% znqg*ZJ*3tdIBXmlNOJbhCP>{}#ZfQ82y=FCgS0Is7aB~A{A+vOWk<4kG8-CsBA>N) z2Ro)Vo9)zRim|LCBI$`F-!JxDQG~E+nVNaMkGbGoHB3M|cbfqm?Jyjr6ln%D z61dqAY5B-YX2WN|HS&_#uo&dO1ZLdVcx6-*l>@yGiUd^twKIQ z1myy3dN1;B0z4enBibGcLp_=&v^1A84wc`CetouQG9=$!N7f##SDg2(;-$ z`!;UT3E!5cpgGLm)#4Fpf{Qj}^JF&E4%N%lmmNV4&oVB`hy6ytSLkp=a!l^3{cMD2 zTZ1ifMFW4}K)*?$c>mDR24g)rEZIEGUiM-d`ALieTX6^VNp)73C?Y9z`9d?=c(?d1 zs~_K-`cOc>&%IHK9z-;#Xp`TMv(d*wB}E%mPIu_y`4;N)(a6iqDI;Sfv%{G`Tq?Y? z`XY5qua{3ZRrAk6vM-O$&0Shch^Vh+#oUI{16*NgkrFgmFX!!x!YeN2Yr^QVW|_o)XG(ZcBN)a|R?) zB#;P8w$4loZCthCwyD)Kv~>DA|AHfFa+EnB3aXYkonv5irz&0+e_1c`|f ziIC%^3DMCrgrvlo!j#n640IkHIfLEfbrQs9Mtu8!_VBgvQKZl*M~Z$T%?|zlVT_2; lV%Z2*hu);Qj4N+cqN`nQhxvX7dAV-`K|Ub$-q+H-5I?Tx0g9jWxd@A|?POE8`3b8fO$T))xP* z(X?&brZw({`)WU&rdAs1iTa0x6F@PIxJ&&L|dpySV!ID|iUhjCcKz(@mE z!x@~W#3H<)4Ae(4eQJRk`Iz3<1)6^m)0b_4_TRZ+cz#eD3f8V;2r-1fE!F}W zEi0MEkTTx}8i1{`l_6vo0(Vuh0HD$I4SjZ=?^?k82R51bC)2D_{y8mi_?X^=U?2|F{Vr7s!k(AZC$O#ZMyavHhlQ7 zUR~QXuH~#o#>(b$u4?s~HLF*3IcF7023AlwAYudn0FV~|odGH^05AYPEfR)8p`i{n zwg3zPVp{+wOsxKc>)(pMupKF!Y2HoUqQ3|Yu|8lwR=?5zZuhG6J?H`bSNk_wPoM{u zSL{c@pY7+c2kck>`^q1^^gR0QB7Y?KUD{vz-uVX~;V-rW)PDcI)$_UjgVV?S?=oLR zf4}zz{#*R_{LkiJ#0RdQLNC^2Vp%JPEUvG9ra2BVZ92(p9h7Ka@!yf9(lj#}>+|u* z;^_?KWdzkM`6gqPo9;;r6&JEa)}R3X{(CWv?NvgLeOTq$cZXqf7|sPImi-7cS8DCN zGf;DVt3Am`>hH3{4-WzH43Ftx)SofNe^-#|0HdCo<+8Qs!}TZP{HH8~z5n`ExcHuT zDL1m&|DVpIy=xsLO>8k92HcmfSKhflQ0H~9=^-{#!I1g(;+44xw~=* zxvNz35vfsQE)@)Zsp*6_GjYD};Squ83<_?^SbALb{a`j<0Gn%6JY!zhp=Fg}Ga2|8 z52e1WU%^L1}15Ex0fF$e@eCT(()_P zvV?CA%#Sy08_U6VPt4EtmVQraWJX` zh=N|WQ>LgrvF~R&qOfB$!%D3cGv?;Xh_z$z7k&s4N)$WYf*k=|*jCEkO19{h_(%W4 zPuOqbCw`SeAX*R}UUsbVsgtuG?xs(#Ikx9`JZoQFz0n*7ZG@Fv@kZk`gzO$HoA9kN z8U5{-yY zvV{`&WKU2$mZeoBmiJrEdzUZAv1sRxpePdg1)F*X^Y)zp^Y*R;;z~vOv-z&)&G)JQ{m!C9cmziu1^nHA z`#`0c>@PnQ9CJKgC5NjJD8HM3|KC(g5nnCq$n0Gsu_DXk36@ql%npEye|?%RmG)

FJ$wK}0tWNB{uH;AM~i literal 0 HcmV?d00001 diff --git a/sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher.png b/sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e5ed46597ea8447d91ab1786a34e30f1c26b18bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4490 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 diff --git a/sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher.webp b/sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..948a3070fe34c611c42c0d3ad3013a0dce358be0 GIT binary patch literal 1900 zcmV-y2b1_xNk&Fw2LJ$9MM6+kP&il$0000G0001A003VA06|PpNH75a00DqwTbm-~ zullQTcXxO9ki!OCRx^i?oR|n!<8G0=kI^!JSjFi-LL*`V;ET0H2IXfU0*i>o6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? literal 0 HcmV?d00001 diff --git a/sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 14ed0af35023e4f1901cf03487b6c524257b8483..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6895 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 diff --git a/sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/sampleAppKotlin/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f GIT binary patch literal 3918 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 diff --git a/sampleAppKotlin/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/sampleAppKotlin/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..28d4b77f9f036a47549d47db79c16788749dca10 GIT binary patch literal 2884 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 XdJ+W}DiIkV00000NkvXXu0mjfKBlg6 diff --git a/sampleAppKotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/sampleAppKotlin/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..9287f5083623b375139afb391af71cc533a7dd37 GIT binary patch literal 5914 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�(p-{x$&=IACIfoAR!za)F-^da-t3#0Dycnp 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 I20H400LcNok^lez diff --git a/sampleAppKotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/sampleAppKotlin/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..aa7d6427e6fa1074b79ccd52ef67ac15c5637e85 GIT binary patch literal 3844 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 diff --git a/sampleAppKotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/sampleAppKotlin/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..9126ae37cbc3587421d6889eadd1d91fbf1994d4 GIT binary patch literal 7778 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

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s literal 0 HcmV?d00001 diff --git a/sampleAppKotlin/src/main/res/values/colors.xml b/sampleAppKotlin/src/main/res/values/colors.xml index b72e5db7..160b4b92 100644 --- a/sampleAppKotlin/src/main/res/values/colors.xml +++ b/sampleAppKotlin/src/main/res/values/colors.xml @@ -4,4 +4,4 @@ #00574B #D81B60 #4398FC - + \ No newline at end of file diff --git a/sampleAppKotlin/src/main/res/values/themes.xml b/sampleAppKotlin/src/main/res/values/themes.xml new file mode 100644 index 00000000..b66862a1 --- /dev/null +++ b/sampleAppKotlin/src/main/res/values/themes.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/sampleAppKotlin/src/main/res/xml/backup_rules.xml b/sampleAppKotlin/src/main/res/xml/backup_rules.xml new file mode 100644 index 00000000..fa0f996d --- /dev/null +++ b/sampleAppKotlin/src/main/res/xml/backup_rules.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/sampleAppKotlin/src/main/res/xml/data_extraction_rules.xml b/sampleAppKotlin/src/main/res/xml/data_extraction_rules.xml new file mode 100644 index 00000000..9ee9997b --- /dev/null +++ b/sampleAppKotlin/src/main/res/xml/data_extraction_rules.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/sampleappjava/.gitignore b/sampleappjava/.gitignore index 796b96d1..42afabfd 100644 --- a/sampleappjava/.gitignore +++ b/sampleappjava/.gitignore @@ -1 +1 @@ -/build +/build \ No newline at end of file diff --git a/sampleappjava/proguard-rules.pro b/sampleappjava/proguard-rules.pro index 3118c700..3d567e74 100644 --- a/sampleappjava/proguard-rules.pro +++ b/sampleappjava/proguard-rules.pro @@ -20,4 +20,4 @@ # hide the original source file name. #-renamesourcefileattribute SourceFile --keep class com.virtusize.android.** +-keep class com.virtusize.android.** \ No newline at end of file diff --git a/sampleappjava/src/androidTest/java/com/virtusize/sampleappjava/ExampleInstrumentedTest.java b/sampleappjava/src/androidTest/java/com/virtusize/sampleappjava/ExampleInstrumentedTest.java deleted file mode 100644 index b2d0f67c..00000000 --- a/sampleappjava/src/androidTest/java/com/virtusize/sampleappjava/ExampleInstrumentedTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.virtusize.sampleappjava; - -import static org.junit.Assert.assertEquals; - -import android.content.Context; -import androidx.test.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getTargetContext(); - - assertEquals("com.virtusize.sampleappjava", appContext.getPackageName()); - } -} diff --git a/sampleappjava/src/main/AndroidManifest.xml b/sampleappjava/src/main/AndroidManifest.xml index f6268a70..e9a21345 100644 --- a/sampleappjava/src/main/AndroidManifest.xml +++ b/sampleappjava/src/main/AndroidManifest.xml @@ -2,13 +2,13 @@ + android:allowBackup="true" + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:roundIcon="@mipmap/ic_launcher_round" + android:supportsRtl="true" + android:theme="@style/AppTheme" + android:name=".App"> diff --git a/sampleappjava/src/main/java/com/virtusize/sampleappjava/App.java b/sampleappjava/src/main/java/com/virtusize/sampleappjava/App.java index 4fca1790..2c2feb89 100644 --- a/sampleappjava/src/main/java/com/virtusize/sampleappjava/App.java +++ b/sampleappjava/src/main/java/com/virtusize/sampleappjava/App.java @@ -1,6 +1,7 @@ package com.virtusize.sampleappjava; import android.app.Application; + import com.virtusize.android.Virtusize; import com.virtusize.android.VirtusizeBuilder; import com.virtusize.android.data.local.VirtusizeEnvironment; diff --git a/sampleappjava/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sampleappjava/src/main/res/drawable-v24/ic_launcher_foreground.xml index 6348baae..2b068d11 100644 --- a/sampleappjava/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ b/sampleappjava/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -1,34 +1,30 @@ - + xmlns:aapt="http://schemas.android.com/aapt" + android:width="108dp" + android:height="108dp" + android:viewportWidth="108" + android:viewportHeight="108"> + + android:endX="85.84757" + android:endY="92.4963" + android:startX="42.9492" + android:startY="49.59793" + android:type="linear"> + android:color="#44000000" + android:offset="0.0" /> + android:color="#00000000" + android:offset="1.0" /> - + android:fillColor="#FFFFFF" + android:fillType="nonZero" + android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" + android:strokeWidth="1" + android:strokeColor="#00000000" /> + \ No newline at end of file diff --git a/sampleappjava/src/main/res/drawable/ic_launcher_background.xml b/sampleappjava/src/main/res/drawable/ic_launcher_background.xml index a0ad202f..07d5da9c 100644 --- a/sampleappjava/src/main/res/drawable/ic_launcher_background.xml +++ b/sampleappjava/src/main/res/drawable/ic_launcher_background.xml @@ -1,74 +1,170 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sampleappjava/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sampleappjava/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index bbd3e021..6f3b755b 100644 --- a/sampleappjava/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/sampleappjava/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,6 @@ - - + + + \ No newline at end of file diff --git a/sampleappjava/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sampleappjava/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index bbd3e021..6f3b755b 100644 --- a/sampleappjava/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/sampleappjava/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,5 +1,6 @@ - - + + + \ No newline at end of file diff --git a/sampleappjava/src/main/res/mipmap-hdpi/ic_launcher.png b/sampleappjava/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 898f3ed59ac9f3248734a00e5902736c9367d455..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2963 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 JPDHLkV1g@XpTGbB diff --git a/sampleappjava/src/main/res/mipmap-hdpi/ic_launcher.webp b/sampleappjava/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c209e78ecd372343283f4157dcfd918ec5165bb3 GIT binary patch literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG literal 0 HcmV?d00001 diff --git a/sampleappjava/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sampleappjava/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index dffca3601eba7bf5f409bdd520820e2eb5122c75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4905 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 literal 0 HcmV?d00001 diff --git a/sampleappjava/src/main/res/mipmap-mdpi/ic_launcher.png b/sampleappjava/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 64ba76f75e9ce021aa3d95c213491f73bcacb597..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2060 zcmV+n2=n)eP)3$g6n-PcZs4>q4bV;KlnN~%kbn}!V8maBKN?~PDN77Zj6xT>KxccMrJYVYoo)adu8>W% zmv*U9KCo@D{=sCEstjFGl{%?R9Bd_S;`C@G{FNG~X;+5Z0h*dJ1r|5g4wB8=?S#Zy zt3sAsXM@aL)nWAyCYz08&uXYp$}38nkeVvA0^C`|ts22ve2Y2>mf~J~_Til&y|FUz z%#l)O^+i>bDr7NsoiC}@GN^5^{=sAkPSF?VF#7ysBZm@DnF?;le_~|Un-B}Itc2u|IlX``0V1M3jKlcCTY73+_+5_^1 zO|_7<%PEyPhbqxCEnFv#uom}FdO$lY%`OKi#h<5Co8ZPBFZA{I!|wAx!c?aisEfxs z?T$*AUTc9D8_Hpt%L37MoudCVml+QIa-Q{X>F$I{4t=051yd2KXJy7g2ho;dPy9%m z&|3%hK)bgG?)N=_y3^l5BAU(HpEX16sc+%jjdr-wd5e*w`^js6LDPj(u<}q7%axih zoQB@MKIp*y%l0*noe!-3>L8Nvz`X|#;P=}%;m-Yg;Pd%Hg6jXkc0~S4=WWP7_Qlvb zG1>9)E0=~O9SWcSdXd@th$;|?3QV+Z@1bR;tdb%M2ko%(GTA+u#e@F7$5Mb+;mB`4 z!xVgv{Jp95%Y!hpT7-)jrQ~&IJFY@h`L?H{0L^~?0CJaZ z{tZjr)sT1m=#VQw^-Fg;S$l@ofMbuY0uykS+-JWJI=h~`ci}FY$50ATJ+%wA zO77DqVS>075^y6_kJfo$5r(}BH#(lkaYNw(n&Hbh&XQd-lYhgIk-UdHhZ4HzOR6cX9O(7$kLq}D}u9EB; z-dhHFDZZ<8Lc2GP(}(AKLrJ-Oau&a1s?6Nk^&FO z6KSRZhEqx_SQs6S0+Eca!Fb^G1gONmI zC+HbyhfVOuc?OI&h7uoNn}=`c_>iW5NO1q-GUX8K1^!Zxzl z4XfveR)GIBSo>}=cI+IH9~|U>#(X~teA-&84{aZTo0BMk;yjBqEL^gX=_9kDnP=}a z`+sm4^17nldnZj&U`51GznG$gf}Fz|OlbvM2~cNtN6bbO;LjW>4doDpXIHr_#-WEK zTp3oTSyarnG|L?64R(Lh#u7IM@+CF;0?j-dAKR%u-gp$bMThf`Y=V%QniZFqb4;b% z+^sU^c~$y+58W}2ds$fqbXadxS)oD}YcBF8+Kmro`dqK7bh9_jZo>N(2|7ZqH?6u% zs@LZQps|*E)s_+u&N{X0R(-hsYauy#KI0bVpUP;&tcc8vw<4D;UKP1mLj0?AU!cHb ztdAKWi}A~qZL?OzGg+1b@q^keUNsrViJ`HuE@E!RO5*b9*&nDxR@U?Q6pMIaj1kMY qJl2nQa+aK&iDQb84*TpHAJ>1BQ$$nT?9A!_0000B%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!T+Hy9+Dw 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

FJ$wK}0tWNB{uH;AM~i literal 0 HcmV?d00001 diff --git a/sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher.png b/sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index e5ed46597ea8447d91ab1786a34e30f1c26b18bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4490 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 diff --git a/sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher.webp b/sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..948a3070fe34c611c42c0d3ad3013a0dce358be0 GIT binary patch literal 1900 zcmV-y2b1_xNk&Fw2LJ$9MM6+kP&il$0000G0001A003VA06|PpNH75a00DqwTbm-~ zullQTcXxO9ki!OCRx^i?oR|n!<8G0=kI^!JSjFi-LL*`V;ET0H2IXfU0*i>o6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? literal 0 HcmV?d00001 diff --git a/sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 14ed0af35023e4f1901cf03487b6c524257b8483..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6895 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 diff --git a/sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/sampleappjava/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f GIT binary patch literal 3918 zcmV-U53%r4Nk&FS4*&pHMM6+kP&il$0000G0001A003VA06|PpNSy@$00HoY|G(*G z+qV7x14$dSO^Re!iqt-AAIE9iwr$(CZQJL$blA4B`>;C3fBY6Q8_YSjb2%a=fc}4E zrSzssacq<^nmW|Rs93PJni30R<8w<(bK_$LO4L?!_OxLl$}K$MUEllnMK|rg=f3;y z*?;3j|Nh>)p0JQ3A~rf(MibH2r+)3cyV1qF&;8m{w-S*y+0mM){KTK^M5}ksc`qX3 zy>rf^b>~l>SSHds8(I@hz3&PD@LmEs4&prkT=BjsBCXTMhN$_)+kvnl0bLKW5rEsj z*d#KXGDB4P&>etx0X+`R19yC=LS)j!mgs5M0L~+o-T~Jl!p!AJxnGAhV%~rhYUL4hlWhgES3Kb5oA&X z{}?3OBSS-{!v$nCIGj->(-TAG)8LR{htr41^gxsT8yqt2@DEG6Yl`Uma3Nd4;YUoW zTbkYl3CMU5ypMF3EIkYmWL|*BknM`0+Kq6CpvO(y$#j94e+q{vI{Zp8cV_6RK!`&C zob$*5Q|$IZ09dW=L!V zw@#2wviu|<#3lgGE8GEhcx+zBt`} zOwP8j9X%^f7i_bth4PiJ$LYtFJSCN$3xwDN;8mr*B;CJwBP2G0TMq0uNt7S^DO_wE zepk!Wrn#Z#03j{`c*Rf~y3o7?J}w?tEELRUR2cgxB*Y{LzA#pxHgf}q?u5idu>077 zd^=p)`nA}6e`|@`p?u}YU66PP_MA}Zqqe!c{nK&z%Jwq1N4e_q<#4g^xaz=ao;u|6 zwpRcW2Lax=ZGbx=Q*HhlJ`Ns#Y*r0*%!T?P*TTiX;rb)$CGLz=rSUum$)3Qyv{BL2 zO*=OI2|%(Yz~`pNEOnLp>+?T@glq-DujlIp?hdJeZ7ctP4_OKx|5@EOps3rr(pWzg zK4d3&oN-X2qN(d_MkfwB4I)_)!I_6nj2iA9u^pQ{;GckGLxBGrJUM2Wdda!k)Y>lq zmjws>dVQ*vW9lvEMkiN3wE-__6OWD0txS&Qn0n22cyj4Q*8(nG4!G{6OOwNvsrPIL zCl-$W9UwkEUVuLwyD%|inbOF*xMODZ4VMEVAq_zUxZ+K#Gdqf!DW$5f)?7UNOFMz! zrB~tuu=6X2FE(p^iqgxr+?ZK;=yz`e;C$#_@D9Lj-+TDVOrva>(#*PVbaHO>A)mhl z07OJWCqYC60518$!&c`eNBcBW%GnfaQ*$eazV^2_AW?j)h;J1nUjN(I9=0+!RVx~% z3@Tf!P0TE+98jA?WceK-}A1% zW!K)lyKcGqy#M~})315-A#2NXQ`?6NR#Apo=S!oF=JfpX>iR*49ec{7AN$xxpK{D$ z2d%Fz&rdfSqourN$~Y^NFIMV1CZ?J*bMx~H3k&meGtH@q9ra2vZxmA$S(#jaaj-g4 ztJmxG+DLV<*q<|sDXPp$X>E)#S}Vm&sRaO5P&goh2><}FEdZSXDqsL$06sAkh(e+v zAsBhKSRexgwg6tIy~GFJzaTxXD(}|+0eOwFDA%rn`X;MVwDHT9=4=g%OaJ9s%3b9>9EUTnnp0t;2Zpa{*>mk~hZqItE_!dQ zOtC>8`$l|mV43Jbudf0N6&&X;{=z}Zi}d1`2qmJ}i|0*GsulD3>GgQXHN)pkR6sf1 z?5ZU%&xtL}oH;YiAA)d*^Ndw2T$+Mjuzyzz@-SM`9df7LqTxLuIwC~S0092~+=qYv z@*ja;?Wt!T!{U?c*Z0YtGe)XbI&y-?B&G2$`JDM)(dIV9G`Sc#6?sI60de6kv+)Qb zUW~2|WjvJq3TA8`0+sWA3zRhY9a~ow)O~&StBkG2{*{TGiY~S8ep{V&Vo2l<6LWsu z^#p0-v*t2?3&aA1)ozu|%efSR=XnpX$lvTeRdKlvM!@|pM5p2w3u-6 zU>}t2xiYLS+{|%C65AzX+23Mtlq?BS&YdYcYsVjoiE&rT>;Necn6l^K)T^lmE`5u{ zm1i+-a-gc;Z&v-{;8r)z6NYfBUv+=_L}ef}qa9FX01)+Aaf+;xj(mL6|JUzGJR1|fnanb%?BPPIp>SCjP|8qE5qJ{=n5ZGw?81z3(k;pzH%1CtlX50{E7h)$h{qGKfzC`e2o`*IqA#tjA z`Fz&^%$b9F*N`)U-#6>a)Z`55`$Dd0cfcs0$d13^ONrdCu9xcv_=n#WQo8stcz3jP9|2EvdI-RhJM3%Q%oM&!OlShM|0 z?gz?wHZSnm45njLtsz8PVT1S&jAlbKg5kVam$p16=EK@Sj4EP0OtH zmJDmdc^v)x>56Qg_wmYHz6h)>kl_h$>0@J!ypv%APmjZTAQVLy6Fu50RGY&JAVNhx zrF_qG6`x9MkT;1SFWo$)l{M$;3qUDn9JwE}z zRl#E_bDRJFii61kPgBybIgp8dNW!Cc1b*^YYk-#oWLJvtM_v^hQx~9?8LD4VFFxBF z3MlrsSC%f9Oupn*ctPL0U1fwfX?`tRhPD{PSLFPQOmIt$mDy0SgpNVvHS+f#Do>h1Gn?LZU9(KaN>Q_=Y*_T zvtD7%_u^^+{g`0VGzg(VZrpVQ6Ub5M=tI_p7T93R8@3Zulu3|#{iNcu!oiHxZ4Rf*( zfmiN$$ru(*_Zqn=`Gq#OuHRTSwp7uH_SokR&|)RuW5yo=Z|_4?qU-JU+tpt>!B&Is z@N(=SG;bpVc;AO@zbmMM zScqq1)b-ZQIrs={oD}|?6y{$HNB1U0^LsBh8JI&3!GBZxOXI<}&5-$lgkAaYqhOTb z?2vEnZ$-kk;*M_17(upJF3%+iH*s0-r{vttXVB2OUwI1s^+G(Ft(U8gYFXC}#P&E^ z>T@C^tS`Z7{6HT4_nF~n>JlZtk5&qDBl6r|^kzQYe`wq!C)n@$c>WOPA61NDFj<<6 zGW71NMMhwAl!U-yqrq2xrSFqRCI8acw7?}3j;ynxo*-b7Co;g5r%^j=H@9({PXXBf z@r>U>>N;E)81wx`B4f%{PB~MHka_);%kBCb(d|Jy5!MqJ%2p`t&@L)4$T2j&-WHvG zv3(uyA_gwqNu(k?jQTtv3dgPKRZoH8prxe7>pQBW5L&dpumS&5Ld2?(sCpJjvc4L5 zEnh&?91WVm)ZdTj=fjJ$pPDdgAttLXuke+?KdKxujaIJ%uU(o!Ypc=Wv%E8e<<)SFdRM{tz(T@!nKT{;0jT2A&dgKu3 zk|GDUX<&73+f+CnZza0G4g29@hmNkl+2wP#$0yi6=u-4CD#*a8LxJLG9KlkveQ7v} z>E#)-tL=xh89y&5li1I!>Zzc!_i6V~nKP^5-+!69FtnX*f=*tr+cf&UpZtLBY|wv< zJ6r*Z5374 zi$7+B3A@szy#|*$Tb~kkzc_N~h3;oe8q95K$w@e#5FRGcF}wXTR}t#^!OnNc>Z52w zu23YrlIQY7UrLLcFSW5ctMBzwrTz=X-m{1Y!*LWUbO~;u&&q8Lu;wlGFqO2h4olL; z{rpPfr}7f=Z)eZhFw1_ITpft-VzPF1CHv-W>u;OCBJBEOEn$HmTpFjX=xN6-H5#V{ zn6Si;q3V*@lFMd>H8;M}vOp8McQcJ}^bBfV`1xb0g0`9ZZa9(wb+L_RGO6wD&I8ouM<}YVDFU ztMSz*yMDz3AkS0YO)3_lYDarEUyj?A#9s@-ln${-1Op^nD7zREi=%4Hy%V?=YS7G`L@>`3kHM4eAD%)t@F};|C zfj?B^Kox-WuPMuDp2=LPZU3Obgnl7{dD>|>*A`fn-0|^8uAHJz;<)tkTXA8lI&dHt&xG(4Il=e~QNN6o9YD7H{TR?17eM>#Z8#Y@_=7fZ?HkZX8i|mEGs5mR`uBi^ zzFh5AG^3EMyvpx(a*)!eOI1?nPTn?v0Ly$)KlQ16Xfrzh+}+Ua_I!5XU@ciwrAZ>O z<7!MU$n6`x${EB6YH$hWOMuSEw+72Lb~rgO*Yp26LGdNp*;^;HAD@(SAr(Dk;j7w! zQ>!M4rxUFYn7E?v7)2q)2rJ2%PY>A>-1O7bY~nt&n)jYnG$(iR#hvlih1p}c)I+|I zy^C;=uIJImfY zL~pm6t6Zw8FiOIY<1>EBS(<5`Cv8DBcZEpTCQ{@@-|2$Bhi;6H?Pofq1Z%b2@)&at zUA{9iaqi62D1|=T{xTe3Czr|z52P;M7EB|V-ss{qspYc0Cj~hUUURef8?i5H?e;kA z<~qW5`JIc(rCLz_oJ~>x8O2IVR%>+7%}`TBSQt%i+m+4tV?z0(?5cf&1v8cNlz7Lg z%ZS>-e!({r)+sH_1+QJvE5BqOgmfK_$X*P0*x6beoRN|0FV zBu+T9^1E5}1I>g&wC|Bn^{(R$!_A@+E4<}3n|QMU=H|GuQZRAZ+zSZ}SS{MNj&mi0 zRY+fp&8IQn-}zGeIVj+qntrIP-IpXF?2xAoyT|i)X+@HL$+|t{#ZAvBrd?L!=9aLy z%@CY;X7U41O6VpHq<1UBk2vi~afo_h1Xrb{vQ%cE|Fvi8EjFCP^~ zabJnB#=NPyBD*BaNSQW*VI+TbEmlu2&HD<4U_UQNUR_`K~u~XWideSoLc(k)vEtG^CT* zG`Zdarw^M&6C=~oi^6W#WL!BMe{E&Gg9Arbg2gg;cO^sJ#+L$ zWBP!R+lcV(p-B#aK<&Ly>?*3fngF)TwSRSmGJ!zET{Brabip#AUPyChm}S9IFG!l{ z%+I_?Cl?zVm9nbGSU`Ksi%z1{vEPpxnv}!StZLIR4yl9y>GM~KIIbNdVs|xsuCpX=J#rE`8<@v*FO%Lb)=#c`~s7W#9EDhRI!G*VBK(y z5D`)jJo4o1={q}Kg%YGhdH~@PGate(xi{(OiQn~MMSZM;!kHNh*1-e<+YS5-j3b?2 zq7SYPWMn1a!^Gqxr4d1gZ5G`QQ(&4Ag*OcnWO}~9rz5xeE3Ycol5cj$@jggn@8x2* z)UpG-U2|Av7a)Hi=b^@SNp#`PEDfswF$nyx&rD*+4SF}`_U48`=1VnBn}aEm{Funk zSWQuC>r8yUkd_D(dKEqo`7i}}{#+a?O4 zDIg~&^q#d5-Ji>``G%gDDzV<~+=*qePTy_lbVjK?!d`>ygnhxwtyL65_G4A=A}{Dh zq;iS@h|Y-wJdeGj1b{KBTkst|klERM7*Hwy#ZO<~Q$5~GzC~WjZHz>=z3~>oAVbbv zzmgOw2JQ#Kv)GT9dwrXGJKz5(Jw%&rYPjfi;TI|dyVJrvaZ*ivGRT;i>R6}8B>7*j zbJi0%9UfLcYKp+TU9qXLSp`rm`)3(g6YOdHa4cv2Y)-JCPZ&g1Z*%F~T@dw@_HA~- zxeq6NeOi{(yh(ziMZ)4yIfDP6nhTg;)$=9N_-{KO!ZB@c@e$(SVH`%0b3YF`lgX)? zmPOF$H%(2yD*LrQ;d*vDgW=s=2h+1RYg?DCXa2gXNT~W+Hu+pBZ$bO8IlS+nqXw^| zBM2iS@v_S^5P@J5V0gw2hamKs7Wro(xWlv)U$%_D)AA{;Mb;l$7?FOK*2{U?f_M(W z4#aOFFlOC*Grkxzi#w)?qgNP48e=dJ*`EYNKfLm6BlZ-j@VMi+{0T>$Y6e%gC|6;v z4=~J;U-H`Rv(<}l7sEXpm?7;(jXl{O>aLca zP;<5GjkKb?74YTOqJAtFKzq|v(-+j{(@?GPIKVS95tsog!>*S60XwAsnYHqG)dW<#@2UIte}({hi5+*r;^rQeDpKps%Ql|LRink z=CR6^g!&1h1Ks5JplDey{0{E~MNPgvQNeH21%lrCFFh~_7#;b73>@zaFo0B}hXo(J z#OVP*a2!ZeK|x0LfazsE0=vAP5xpQ58{e}Xtzn5B`l%b)PM2PI{UmZ`}XbW%4eE=4-VAbQ|zojxNh6BnLDzTlx-stKQP0|=pi5R7qw0g}ivih_z$ zN`Pc6h9K3P5vFz^s^};EaGwq5yEdpH4Um!3Lju85e*w5hg)|yEkihSklp#pqhWjij zaK_T%_)PG>g`7N9$25qwhR3WB{&pp8G2;J-#qe6%xdFHO2AeceqW`Q#`J1X4*a>V4 z;Y4EVTMA!^vxOA;$ZDCt!CPots~0yn*Erio(G!n)@W*|^D_=Wy;f*k=tF~9Zmr)dn zCzfODoJ@UXXs>1NP-A4#YmmhGXavn<+z_gJ`>cZaGo@Iz2J)=M7{{ zJ;n45y6T86%gls;?`*1bFl=sXf1H<+2AiBU`}H6YM=+eFPoz%Sg=s>Dva{ls1mJO? zTWP*i(U7Ec^3%Z$g`f%l##*mSt_wOa-d&(0A0@(ms#pY$P8SX-ZAVg)> zpsk00`SNH__*AQ#=>~|-wScS`e>RBCs6NsQ18sz`Q({qI(fOQUY10Mt%YO^v{>w>TEBSR zi>oS_n(}3A8W+^iWG~}cr3Bv#s3W>CFUJm0ejS>=V^X>!UmDV@|xH@hWB5yhc zuXagN9&cY%tMFc@?PqIxYmy+OSGU`O5gvK2Yaic7tFAiaz`*T*dLafG4tz~<{L=*n z1iRA9k6#TYhCWcSFW6P4&4yOea4q&Fy6Mbkfl&!{&@KmDXMWs7;2Q2bRU~gBtDs>o zNeUgzt#lWV4oq=C=5{Id0)=a+u5HaCtDZwXnX5u!bO%{LbXF-L40}KeG4lG*uU{E_AOMMd4ch=Q9&rc=;3fB`I@EFBuF!XcuT783*FH`4zO zxZ=AOG#fzwnh^u6!|A7Fqf5u{$IesB&EF?V9g5dyhcmbVh)|M3^!U*}qJEYbGFaK2 z#0I`dWniJzl~+;sJs^jty%7`^Yv#{r+=Q<#CleH22pEWpQ)lwX9b5uv064&fPlS+b zqZM<&o~(2`QgUJ$O29zuo%|4(uP+zAeibd;jfc(zz|+6+9EUrZ?#^|ymX-knV0Dsz zFn=Bg(*p-JjWR}+{_C#CZ~dR&on|-C9&{&ij%~0x9gtgIMPCkr_rc{WE_}pL*bCnZ z3d?M3AYq3)iUS7jPOFD3m9DVG)E&SJ1*`YXzZQib9R(``({n~0aGXEhgZnJU3vy*N zlEAeqef_?@nqICTH{?wuZFw#7F{`&i?NLpf<7G2noyziDxMHBmK=Z&P8jf>~^fSVF zFmD1h)DVg7D8erkb}OkfElv2i`s#7j5-;7~&l>SlgLRqNM90B`oFJ!3Z!I+~g7^$B zkD<7Y^U2QID5DVT!a*uS%0aL5KAD#Lk5^|WCC!!OQcFyxCl$386q*ohKGP#?pNL0_ zG0d|NfxU%N?);5-{u0rA@S7+4>7&sDwppXmJaj`?8D#?9@k90l(a-Vg>E`q1zXh9B zEsyo)21!OKE@yf_^P?a!d>O%I$~z&Bg| z{KuO5lVh07O|keMJh@ks$3EfHm`nFk6qNS&_PxPbKN1c~Ds8?;y>OzV;B0$XVQ=LQx12PJ2~x!&?qm%Tl)eivoas}<)&`&84*`tT{?ou45c+RPjX;imIsuwmXJs;5Klbii3#Q0kSLKcW+Y@xKcRce+GJ-RTlpMp(c)D`xrv zd|#_rj!Bm<&cad=Pq($+uKOY#CGCK-8EXOLAo{LJ2l({+_%87YR(e2EErULI*gm@X z*m6LuczdHTQHH`3=)x;unt9KH-4duW3nu}xk&Cu4-DS4wjNG}S$tO5H_$l1*S3Go6 z0HH1rN4WcDUK${}+a@ICZ(ZC#*`6h6EK7)q2OePook_w)c5%-9AxwoT6E*>!XDxpM zy_C$yP!`aN2TiCVLn_z`_E((J%LUYuw%2%(GBL3Cve+5zmepidD|^#$=@2Wfp!?NR zUpV2SwaMg68}9+`X#n-Ust|TK-Qk@HXu7dM*@>KO~@YA_S!geT; zxLp>TbIo9^WI=ZuT?ErRN;LqRSZX$7)+{MdSSiDnSdSwQ+6Yqb#nF393O_Ow-rRZD z1MtC55vP=~4kwe+$#2C8b3Q6*<^!T_D^X($HS$*Ns2(pd5~m<_QgfsetRt77rwh}yjg#yx`@p|%;RnzvAN8~6i5D;EQg*azSU-+F9W;M>-%sM=r4J zY%}@{t+!2883WSGMgw_85U#I}O75Rr0Q_D5;Du8|l@ zHWBq-r2&(pezi>6+daPx-qwVIQ3A6$h}GxIH72G*;HeRgyXKy?Uf!HvVg$M3Vs?lo j7HB*8-{6~e<}KKy%g|C8?m&3=nE}vH(NX@WXdCq(XawjJ diff --git a/sampleappjava/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/sampleappjava/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..28d4b77f9f036a47549d47db79c16788749dca10 GIT binary patch literal 2884 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 XdJ+W}DiIkV00000NkvXXu0mjfKBlg6 diff --git a/sampleappjava/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/sampleappjava/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..9287f5083623b375139afb391af71cc533a7dd37 GIT binary patch literal 5914 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�(p-{x$&=IACIfoAR!za)F-^da-t3#0Dycnp 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 I20H400LcNok^lez diff --git a/sampleappjava/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/sampleappjava/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..aa7d6427e6fa1074b79ccd52ef67ac15c5637e85 GIT binary patch literal 3844 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 diff --git a/sampleappjava/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/sampleappjava/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..9126ae37cbc3587421d6889eadd1d91fbf1994d4 GIT binary patch literal 7778 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

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s literal 0 HcmV?d00001 diff --git a/sampleappjava/src/main/res/values/strings.xml b/sampleappjava/src/main/res/values/strings.xml index a3e1eb28..948a144a 100644 --- a/sampleappjava/src/main/res/values/strings.xml +++ b/sampleappjava/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - Sample App Java + Sample App Java diff --git a/sampleappjava/src/main/res/values/styles.xml b/sampleappjava/src/main/res/values/styles.xml deleted file mode 100644 index 5885930d..00000000 --- a/sampleappjava/src/main/res/values/styles.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/sampleAppKotlin/src/main/res/values/styles.xml b/sampleappjava/src/main/res/values/themes.xml similarity index 100% rename from sampleAppKotlin/src/main/res/values/styles.xml rename to sampleappjava/src/main/res/values/themes.xml diff --git a/sampleappjava/src/test/java/com/virtusize/sampleappjava/ExampleUnitTest.java b/sampleappjava/src/test/java/com/virtusize/sampleappjava/ExampleUnitTest.java deleted file mode 100644 index 6eaa3f33..00000000 --- a/sampleappjava/src/test/java/com/virtusize/sampleappjava/ExampleUnitTest.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.virtusize.sampleappjava; - -import static org.junit.Assert.assertEquals; - -import org.junit.Test; - -/** - * Example local unit test, which will execute on the development machine (host). - * - * @see Testing documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/virtusize-core/src/main/java/com/virtusize/android/SharedPreferencesHelper.kt b/virtusize-core/src/main/java/com/virtusize/android/SharedPreferencesHelper.kt index 6012582f..e3adfa5f 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/SharedPreferencesHelper.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/SharedPreferencesHelper.kt @@ -9,7 +9,6 @@ import kotlin.random.Random * This class is used to store and get the browser identifier and user auth data specific to this SDK */ class SharedPreferencesHelper { - companion object { private const val SHARED_PREFS_NAME = "VIRTUSIZE_SHARED_PREFS" private const val PREFS_BID_KEY = "BID_KEY_VIRTUSIZE" @@ -22,10 +21,11 @@ class SharedPreferencesHelper { // Gets the instance of [SharedPreferencesHelper] fun getInstance(context: Context): SharedPreferencesHelper { - preferences = context.getSharedPreferences( - SHARED_PREFS_NAME, - Context.MODE_PRIVATE - ) + preferences = + context.getSharedPreferences( + SHARED_PREFS_NAME, + Context.MODE_PRIVATE, + ) if (sharedPreferenceHelper == null) { sharedPreferenceHelper = SharedPreferencesHelper() 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 c9672189..4a0d27c7 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 @@ -18,9 +18,8 @@ import org.json.JSONObject internal data class BodyProfileRecommendedSizeParams constructor( private val productTypes: List, private val storeProduct: Product, - private val userBodyProfile: UserBodyProfile + private val userBodyProfile: UserBodyProfile, ) { - /** * Returns the map that represents the API request body * @return the name of the event @@ -28,39 +27,39 @@ internal data class BodyProfileRecommendedSizeParams constructor( fun paramsToMap(): Map { return emptyMap() .plus( - mapOf(PARAM_BODY_DATA to createBodyDataParams()) + mapOf(PARAM_BODY_DATA to createBodyDataParams()), ) .plus( - mapOf(PARAM_USER_GENDER to userBodyProfile.gender) + mapOf(PARAM_USER_GENDER to userBodyProfile.gender), ) .plus( - mapOf(PARAM_USER_HEIGHT to userBodyProfile.height) + mapOf(PARAM_USER_HEIGHT to userBodyProfile.height), ) .plus( userBodyProfile.weight.toFloatOrNull()?.let { mapOf(PARAM_USER_WEIGHT to it) } - .orEmpty() + .orEmpty(), ) .plus( - mapOf(PARAM_ITEMS to arrayOf(createItemsParams())) + mapOf(PARAM_ITEMS to arrayOf(createItemsParams())), ) } private fun createItemsParams(): Map { return emptyMap() .plus( - mapOf(PARAM_ITEM_SIZES to createItemSizesParams()) + mapOf(PARAM_ITEM_SIZES to createItemSizesParams()), ) .plus( mapOf( PARAM_PRODUCT_TYPE to - (productTypes.find { it.id == storeProduct.productType }?.name ?: "") - ) + (productTypes.find { it.id == storeProduct.productType }?.name ?: ""), + ), ) .plus( - mapOf(PARAM_ADDITIONAL_INFO to createAdditionalInfoParams()) + mapOf(PARAM_ADDITIONAL_INFO to createAdditionalInfoParams()), ) .plus( - mapOf(PARAM_EXTERNAL_PRODUCT_ID to (storeProduct.externalId ?: "")) + mapOf(PARAM_EXTERNAL_PRODUCT_ID to (storeProduct.externalId ?: "")), ) } @@ -68,33 +67,36 @@ internal data class BodyProfileRecommendedSizeParams constructor( * Creates the map that represents the store product additional info */ fun createAdditionalInfoParams(): Map { - val brand = storeProduct.storeProductMeta?.additionalInfo?.brand - ?: storeProduct.storeProductMeta?.brand - val sizeHashMap = storeProduct.storeProductMeta?.additionalInfo?.sizes?.associate { - it.name to it.measurements.associate { measurement -> - measurement.name to measurement.millimeter + val brand = + storeProduct.storeProductMeta?.additionalInfo?.brand + ?: storeProduct.storeProductMeta?.brand + val sizeHashMap = + storeProduct.storeProductMeta?.additionalInfo?.sizes?.associate { + it.name to + it.measurements.associate { measurement -> + measurement.name to measurement.millimeter + } } - } return emptyMap() .plus( - mapOf(PARAM_BRAND to (brand ?: "")) + mapOf(PARAM_BRAND to (brand ?: "")), ) .plus( mapOf( - PARAM_FIT to (storeProduct.storeProductMeta?.additionalInfo?.fit ?: "regular") - ) + PARAM_FIT to (storeProduct.storeProductMeta?.additionalInfo?.fit ?: "regular"), + ), ) .plus( - mapOf(PARAM_SIZES to (sizeHashMap ?: mutableMapOf())) + mapOf(PARAM_SIZES to (sizeHashMap ?: mutableMapOf())), ) .plus( mapOf( PARAM_MODEL_INFO to - (storeProduct.storeProductMeta?.additionalInfo?.modelInfo ?: JSONObject.NULL) - ) + (storeProduct.storeProductMeta?.additionalInfo?.modelInfo ?: JSONObject.NULL), + ), ) .plus( - mapOf(PARAM_GENDER to userBodyProfile.gender) + mapOf(PARAM_GENDER to userBodyProfile.gender), ) } @@ -109,21 +111,23 @@ internal data class BodyProfileRecommendedSizeParams constructor( if (it.name == PARAM_BODY_BUST) { chestValue = it.millimeter } - it.name to mutableMapOf( - PARAM_BODY_MEASUREMENT_VALUE to it.millimeter, - PARAM_BODY_MEASUREMENT_PREDICTED to true - ) - } + it.name to + mutableMapOf( + PARAM_BODY_MEASUREMENT_VALUE to it.millimeter, + PARAM_BODY_MEASUREMENT_PREDICTED to true, + ) + }, ) .plus( chestValue?.let { mapOf( - PARAM_BODY_CHEST to mutableMapOf( - PARAM_BODY_MEASUREMENT_VALUE to it, - PARAM_BODY_MEASUREMENT_PREDICTED to true - ) + PARAM_BODY_CHEST to + mutableMapOf( + PARAM_BODY_MEASUREMENT_VALUE to it, + PARAM_BODY_MEASUREMENT_PREDICTED to true, + ), ) - }.orEmpty() + }.orEmpty(), ) } @@ -134,10 +138,11 @@ internal data class BodyProfileRecommendedSizeParams constructor( return emptyMap() .plus( storeProduct.sizes.map { productSize -> - productSize.name to productSize.measurements.associate { measurement -> - measurement.name to measurement.millimeter - } - } + productSize.name to + productSize.measurements.associate { measurement -> + measurement.name to measurement.millimeter + } + }, ) } diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/SizeComparisonRecommendedSize.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/SizeComparisonRecommendedSize.kt index 7a311243..96227a8d 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/SizeComparisonRecommendedSize.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/SizeComparisonRecommendedSize.kt @@ -16,5 +16,5 @@ data class SizeComparisonRecommendedSize( var bestFitScore: Float = 0f, var bestStoreProductSize: ProductSize? = null, var bestUserProduct: Product? = null, - var isStoreProductSmaller: Boolean? = false + var isStoreProductSmaller: Boolean? = false, ) diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeEnvironment.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeEnvironment.kt index 57e5ccb8..ffa5d903 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeEnvironment.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeEnvironment.kt @@ -8,7 +8,7 @@ enum class VirtusizeEnvironment { STAGING, GLOBAL, JAPAN, - KOREA + KOREA, } /** @@ -70,9 +70,11 @@ fun VirtusizeEnvironment.sizeRecommendationApiBaseUrl(): String { fun VirtusizeEnvironment.virtusizeUrl(): String { return when (this) { VirtusizeEnvironment.STAGING, - VirtusizeEnvironment.GLOBAL -> "https://static.api.virtusize.com" + VirtusizeEnvironment.GLOBAL, + -> "https://static.api.virtusize.com" VirtusizeEnvironment.TESTING, - VirtusizeEnvironment.JAPAN -> "https://static.api.virtusize.jp" + VirtusizeEnvironment.JAPAN, + -> "https://static.api.virtusize.jp" VirtusizeEnvironment.KOREA -> "https://static.api.virtusize.kr" } } diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeError.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeError.kt index 612c9720..d9a8ed6d 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeError.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeError.kt @@ -10,5 +10,5 @@ package com.virtusize.android.data.local data class VirtusizeError( val type: VirtusizeErrorType? = null, val code: Int? = null, - val message: String + val message: String, ) diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeErrorType.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeErrorType.kt index b38b1ee3..86880d9b 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeErrorType.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeErrorType.kt @@ -18,7 +18,7 @@ enum class VirtusizeErrorType { APIError, JsonParsingError, WardrobeNotFound, - PrivacyLinkNotOpen + PrivacyLinkNotOpen, } /** @@ -30,7 +30,8 @@ fun VirtusizeErrorType.code(): Int? { VirtusizeErrorType.ApiKeyNullOrInvalid -> HttpURLConnection.HTTP_FORBIDDEN VirtusizeErrorType.InvalidProduct, VirtusizeErrorType.UnParsedProduct, - VirtusizeErrorType.WardrobeNotFound -> HttpURLConnection.HTTP_NOT_FOUND + VirtusizeErrorType.WardrobeNotFound, + -> HttpURLConnection.HTTP_NOT_FOUND else -> null } } @@ -76,7 +77,7 @@ fun VirtusizeErrorType.message(extraMessage: String? = null): String { */ fun VirtusizeErrorType.virtusizeError( code: Int? = null, - extraMessage: String? = null + extraMessage: String? = null, ): VirtusizeError { return VirtusizeError(this, code ?: this.code(), this.message(extraMessage)) } diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeEvent.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeEvent.kt index fef51abe..b3594811 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeEvent.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeEvent.kt @@ -18,7 +18,7 @@ enum class VirtusizeEvents { UserAuthData, UserLoggedIn, UserLoggedOut, - UserDeletedData + UserDeletedData, } /** @@ -48,8 +48,8 @@ fun VirtusizeEvents.getEventName(): String { * Based on a user's selection of the type in the web view, the SDK displays a corresponding InPage comparison */ enum class SizeRecommendationType { - body, - compareProduct + Body, + CompareProduct, } /** diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeLanguage.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeLanguage.kt index a3327381..a03f4b66 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeLanguage.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeLanguage.kt @@ -6,5 +6,5 @@ package com.virtusize.android.data.local enum class VirtusizeLanguage(val value: String, val label: String) { EN("en", "English"), JP("ja", "日本語"), - KR("ko", "한국어") + KR("ko", "한국어"), } diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeMessageHandler.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeMessageHandler.kt index fd52e4a8..62d2446e 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeMessageHandler.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeMessageHandler.kt @@ -4,6 +4,10 @@ package com.virtusize.android.data.local * This interface can be implemented by an object to receive Virtusize specific messages */ interface VirtusizeMessageHandler { - fun onEvent(product: VirtusizeProduct, event: VirtusizeEvent) + fun onEvent( + product: VirtusizeProduct, + event: VirtusizeEvent, + ) + fun onError(error: VirtusizeError) } diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeOrder.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeOrder.kt index d3e543f2..e63f09dd 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeOrder.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeOrder.kt @@ -6,67 +6,72 @@ package com.virtusize.android.data.local * @param externalOrderId the order ID provided by the client * @param items a list of the order items. See [VirtusizeOrderItem] */ -data class VirtusizeOrder @JvmOverloads constructor( - private val externalOrderId: String, - var items: MutableList = mutableListOf() -) { - - /** - * A country code is set for each region i.e. ISO-3166. - * This is set by the response of the request that retrieves the specific store info - */ - private var region: String? = null - - /** - * Returns the map that represents the parameters for the request that retrieves the specific store info - * @param apiKey the API key that is unique to every Virtusize Client - * @param externalUserId the unique user ID from the client system. It should be set during the initialization of the [Virtusize] class - */ - fun paramsToMap(apiKey: String, externalUserId: String): Map { - return emptyMap() - .plus( - mapOf(PARAM_API_KEY to apiKey) - ) - .plus( - mapOf(PARAM_EXTERNAL_ORDER_ID to externalOrderId) - ) - .plus( - mapOf(PARAM_EXTERNAL_USER_ID to externalUserId) - ) - .plus( - region?.let { mapOf(PARAM_REGION to it) }.orEmpty() - ) - .plus( - mapOf(PARAM_ITEMS to items.map { it.paramsToMap() }) - ) - } - - /** - * Sets the string value of the region - */ - fun setRegion(region: String?) { - this.region = region - } +data class VirtusizeOrder + @JvmOverloads + constructor( + private val externalOrderId: String, + var items: MutableList = mutableListOf(), + ) { + /** + * A country code is set for each region i.e. ISO-3166. + * This is set by the response of the request that retrieves the specific store info + */ + private var region: String? = null - companion object { - private const val PARAM_API_KEY = "apiKey" - private const val PARAM_EXTERNAL_ORDER_ID = "externalOrderId" - private const val PARAM_EXTERNAL_USER_ID = "externalUserId" - private const val PARAM_REGION = "region" - private const val PARAM_ITEMS = "items" + /** + * Returns the map that represents the parameters for the request that retrieves the specific store info + * @param apiKey the API key that is unique to every Virtusize Client + * @param externalUserId the unique user ID from the client system. It should be set during the initialization of the [Virtusize] class + */ + fun paramsToMap( + apiKey: String, + externalUserId: String, + ): Map { + return emptyMap() + .plus( + mapOf(PARAM_API_KEY to apiKey), + ) + .plus( + mapOf(PARAM_EXTERNAL_ORDER_ID to externalOrderId), + ) + .plus( + mapOf(PARAM_EXTERNAL_USER_ID to externalUserId), + ) + .plus( + region?.let { mapOf(PARAM_REGION to it) }.orEmpty(), + ) + .plus( + mapOf(PARAM_ITEMS to items.map { it.paramsToMap() }), + ) + } /** - * Parses a Map of the order data to a [VirtusizeOrder] object + * Sets the string value of the region */ - fun parseMap(orderMap: Map): VirtusizeOrder { - return VirtusizeOrder( - externalOrderId = orderMap[PARAM_EXTERNAL_ORDER_ID] as String, - items = (orderMap[PARAM_ITEMS] as List>).map { orderItemMap -> - VirtusizeOrderItem.parseMap( - orderItemMap - ) - }.toMutableList() - ) + fun setRegion(region: String?) { + this.region = region + } + + companion object { + private const val PARAM_API_KEY = "apiKey" + private const val PARAM_EXTERNAL_ORDER_ID = "externalOrderId" + private const val PARAM_EXTERNAL_USER_ID = "externalUserId" + private const val PARAM_REGION = "region" + private const val PARAM_ITEMS = "items" + + /** + * Parses a Map of the order data to a [VirtusizeOrder] object + */ + fun parseMap(orderMap: Map): VirtusizeOrder { + return VirtusizeOrder( + externalOrderId = orderMap[PARAM_EXTERNAL_ORDER_ID] as String, + items = + (orderMap[PARAM_ITEMS] as List>).map { orderItemMap -> + VirtusizeOrderItem.parseMap( + orderItemMap, + ) + }.toMutableList(), + ) + } } } -} diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeOrderItem.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeOrderItem.kt index 40d5fa8a..bcceede5 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeOrderItem.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeOrderItem.kt @@ -15,90 +15,91 @@ package com.virtusize.android.data.local * @param url the URL of the product page. Please make sure this is a URL that users can access. * */ -data class VirtusizeOrderItem @JvmOverloads constructor( - private val productId: String, - private val size: String, - private val sizeAlias: String? = null, - private val variantId: String? = null, - private val imageUrl: String, - private val color: String? = null, - private val gender: String? = null, - private val unitPrice: Double, - private val currency: String, - private val quantity: Int = 1, - private val url: String? = null -) { - - /** - * Returns the map that represents the parameters for the order request - */ - fun paramsToMap(): Map { - return emptyMap() - .plus( - mapOf(PARAM_PRODUCT_ID to productId) - ) - .plus( - mapOf(PARAM_SIZE to size) - ) - .plus( - sizeAlias?.let { mapOf(PARAM_SIZE_ALIAS to it) }.orEmpty() - ) - .plus( - variantId?.let { mapOf(PARAM_VARIANT_ID to it) }.orEmpty() - ) - .plus( - mapOf(PARAM_IMAGE_URL to imageUrl) - ) - .plus( - color?.let { mapOf(PARAM_COLOR to it) }.orEmpty() - ) - .plus( - gender?.let { mapOf(PARAM_GENDER to it) }.orEmpty() - ) - .plus( - mapOf(PARAM_UNIT_PRICE to "%.2f".format(unitPrice).toDouble()) - ) - .plus( - mapOf(PARAM_CURRENCY to currency) - ) - .plus( - mapOf(PARAM_QUANTITY to quantity) - ) - .plus( - url?.let { mapOf(PARAM_URL to it) }.orEmpty() - ) - } - - companion object { - private const val PARAM_PRODUCT_ID = "externalProductId" - private const val PARAM_SIZE = "size" - private const val PARAM_SIZE_ALIAS = "sizeAlias" - private const val PARAM_VARIANT_ID = "variantId" - private const val PARAM_IMAGE_URL = "imageUrl" - private const val PARAM_COLOR = "color" - private const val PARAM_GENDER = "gender" - private const val PARAM_UNIT_PRICE = "unitPrice" - private const val PARAM_CURRENCY = "currency" - private const val PARAM_QUANTITY = "quantity" - private const val PARAM_URL = "url" - +data class VirtusizeOrderItem + @JvmOverloads + constructor( + private val productId: String, + private val size: String, + private val sizeAlias: String? = null, + private val variantId: String? = null, + private val imageUrl: String, + private val color: String? = null, + private val gender: String? = null, + private val unitPrice: Double, + private val currency: String, + private val quantity: Int = 1, + private val url: String? = null, + ) { /** - * Parses a Map of the order item data to a [VirtusizeOrderItem] object + * Returns the map that represents the parameters for the order request */ - internal fun parseMap(orderItemMap: Map): VirtusizeOrderItem { - return VirtusizeOrderItem( - productId = orderItemMap[PARAM_PRODUCT_ID] as String, - size = orderItemMap[PARAM_SIZE] as String, - sizeAlias = orderItemMap[PARAM_SIZE_ALIAS] as? String, - variantId = orderItemMap[PARAM_VARIANT_ID] as? String, - imageUrl = orderItemMap[PARAM_IMAGE_URL] as String, - color = orderItemMap[PARAM_COLOR] as? String, - gender = orderItemMap[PARAM_GENDER] as? String, - unitPrice = orderItemMap[PARAM_UNIT_PRICE] as Double, - currency = orderItemMap[PARAM_CURRENCY] as String, - quantity = orderItemMap[PARAM_QUANTITY] as Int, - url = orderItemMap[PARAM_URL] as? String - ) + fun paramsToMap(): Map { + return emptyMap() + .plus( + mapOf(PARAM_PRODUCT_ID to productId), + ) + .plus( + mapOf(PARAM_SIZE to size), + ) + .plus( + sizeAlias?.let { mapOf(PARAM_SIZE_ALIAS to it) }.orEmpty(), + ) + .plus( + variantId?.let { mapOf(PARAM_VARIANT_ID to it) }.orEmpty(), + ) + .plus( + mapOf(PARAM_IMAGE_URL to imageUrl), + ) + .plus( + color?.let { mapOf(PARAM_COLOR to it) }.orEmpty(), + ) + .plus( + gender?.let { mapOf(PARAM_GENDER to it) }.orEmpty(), + ) + .plus( + mapOf(PARAM_UNIT_PRICE to "%.2f".format(unitPrice).toDouble()), + ) + .plus( + mapOf(PARAM_CURRENCY to currency), + ) + .plus( + mapOf(PARAM_QUANTITY to quantity), + ) + .plus( + url?.let { mapOf(PARAM_URL to it) }.orEmpty(), + ) + } + + companion object { + private const val PARAM_PRODUCT_ID = "externalProductId" + private const val PARAM_SIZE = "size" + private const val PARAM_SIZE_ALIAS = "sizeAlias" + private const val PARAM_VARIANT_ID = "variantId" + private const val PARAM_IMAGE_URL = "imageUrl" + private const val PARAM_COLOR = "color" + private const val PARAM_GENDER = "gender" + private const val PARAM_UNIT_PRICE = "unitPrice" + private const val PARAM_CURRENCY = "currency" + private const val PARAM_QUANTITY = "quantity" + private const val PARAM_URL = "url" + + /** + * Parses a Map of the order item data to a [VirtusizeOrderItem] object + */ + internal fun parseMap(orderItemMap: Map): VirtusizeOrderItem { + return VirtusizeOrderItem( + productId = orderItemMap[PARAM_PRODUCT_ID] as String, + size = orderItemMap[PARAM_SIZE] as String, + sizeAlias = orderItemMap[PARAM_SIZE_ALIAS] as? String, + variantId = orderItemMap[PARAM_VARIANT_ID] as? String, + imageUrl = orderItemMap[PARAM_IMAGE_URL] as String, + color = orderItemMap[PARAM_COLOR] as? String, + gender = orderItemMap[PARAM_GENDER] as? String, + unitPrice = orderItemMap[PARAM_UNIT_PRICE] as Double, + currency = orderItemMap[PARAM_CURRENCY] as String, + quantity = orderItemMap[PARAM_QUANTITY] as Int, + url = orderItemMap[PARAM_URL] as? String, + ) + } } } -} diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeProduct.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeProduct.kt index f9e7c341..e09e7091 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeProduct.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeProduct.kt @@ -12,8 +12,10 @@ import kotlinx.android.parcel.Parcelize * @param productCheckData the product check response from Virtusize API */ @Parcelize -data class VirtusizeProduct @JvmOverloads constructor( - val externalId: String, - var imageUrl: String? = null, - var productCheckData: ProductCheck? = null -) : Parcelable +data class VirtusizeProduct + @JvmOverloads + constructor( + val externalId: String, + var imageUrl: String? = null, + var productCheckData: ProductCheck? = null, + ) : Parcelable diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeRegion.kt b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeRegion.kt index 93a878db..238dba78 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeRegion.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/local/VirtusizeRegion.kt @@ -18,7 +18,7 @@ enum class VirtusizeRegion(val value: String) { override fun defaultLanguage(): VirtusizeLanguage { return VirtusizeLanguage.KR } - }; + }, ; /** * The abstract method to get the default display language of the Virtusize web app corresponding to the [VirtusizeRegion] value diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/DataJsonParser.kt b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/DataJsonParser.kt index 4f0e7193..26760006 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/DataJsonParser.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/DataJsonParser.kt @@ -10,9 +10,10 @@ internal class DataJsonParser : VirtusizeJsonParser { override fun parse(json: JSONObject): Data? { val validProduct = json.optBoolean(FIELD_VALID_PRODUCT) val fetchMetaData = json.optBoolean(FIELD_FETCH_META_DATA) - val shouldSeePhTooltip = json.optJSONObject(FIELD_USER_DATA) - ?.optBoolean(FIELD_SHOULD_SEE_PH_TOOLTIP) - ?: false + val shouldSeePhTooltip = + json.optJSONObject(FIELD_USER_DATA) + ?.optBoolean(FIELD_SHOULD_SEE_PH_TOOLTIP) + ?: false val productDataId = json.optInt(FIELD_PRODUCT_DATA_ID) val productTypeName = JsonUtils.optString(json, FIELD_PRODUCT_TYPE_NAME) val storeName = JsonUtils.optString(json, FIELD_STORE_NAME) @@ -26,7 +27,7 @@ internal class DataJsonParser : VirtusizeJsonParser { productTypeName, storeName, storeId, - productTypeId + productTypeId, ) } diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/JsonUtils.kt b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/JsonUtils.kt index 5979ef57..00c62c61 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/JsonUtils.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/JsonUtils.kt @@ -11,7 +11,6 @@ import kotlin.collections.HashMap * JSON parsing utility functions */ object JsonUtils { - /** * Returns the String value mapped by name. If it isn't present, return an empty string * @@ -19,7 +18,10 @@ object JsonUtils { * @param name the optional field name * @return the value stored in the field. If it isn't present, it returns an empty string */ - fun optString(jsonObject: JSONObject, name: String?): String { + fun optString( + jsonObject: JSONObject, + name: String?, + ): String { val stringValue = optNullableString(jsonObject, name) return stringValue ?: "" } @@ -31,7 +33,10 @@ object JsonUtils { * @param name the optional field name * @return the value stored in the field. If it isn't present, it returns null */ - fun optNullableString(jsonObject: JSONObject, name: String?): String? { + fun optNullableString( + jsonObject: JSONObject, + name: String?, + ): String? { val stringValue = jsonObject.optString(name) return if (stringValue == "null") null else stringValue } @@ -48,21 +53,24 @@ object JsonUtils { while (keys.hasNext()) { val key = keys.next() jsonObject.opt(key)?.let { value -> - map[key] = when (value) { - is JSONObject -> jsonObjectToMap( - value - ) - is JSONArray -> jsonArrayToList( - value - ) - else -> { - if (value is BigDecimal) { - value.toDouble() - } else { - value + map[key] = + when (value) { + is JSONObject -> + jsonObjectToMap( + value, + ) + is JSONArray -> + jsonArrayToList( + value, + ) + else -> { + if (value is BigDecimal) { + value.toDouble() + } else { + value + } } } - } } } return map @@ -99,12 +107,12 @@ object JsonUtils { if (value is JSONArray) { value = jsonArrayToList( - value + value, ) } else if (value is JSONObject) { value = jsonObjectToMap( - value + value, ) } if (value is BigDecimal) { diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductCheckJsonParser.kt b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductCheckJsonParser.kt index 7b8ed668..e0ef6503 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductCheckJsonParser.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductCheckJsonParser.kt @@ -8,9 +8,10 @@ import org.json.JSONObject */ class ProductCheckJsonParser : VirtusizeJsonParser { override fun parse(json: JSONObject): ProductCheck { - val data = json.optJSONObject(FIELD_DATA)?.let { - DataJsonParser().parse(it) - } + val data = + json.optJSONObject(FIELD_DATA)?.let { + DataJsonParser().parse(it) + } val productId = JsonUtils.optString(json, FIELD_PRODUCT_ID) val name = JsonUtils.optString(json, FIELD_NAME) return ProductCheck(data, productId, name, json.toString()) diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductJsonParser.kt b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductJsonParser.kt index fa027cf8..b1c01247 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductJsonParser.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductJsonParser.kt @@ -8,14 +8,14 @@ import org.json.JSONObject * This class parses a JSONObject to the [Product] object */ open class ProductJsonParser : VirtusizeJsonParser { - override fun parse(json: JSONObject): Product? { val id = json.optInt(FIELD_ID) var sizes = emptyList() json.optJSONArray(FIELD_SIZES)?.let { jsonArray -> - sizes = (0 until jsonArray.length()) - .map { idx -> jsonArray.getJSONObject(idx) } - .mapNotNull { ProductSizeJsonParser().parse(it) } + sizes = + (0 until jsonArray.length()) + .map { idx -> jsonArray.getJSONObject(idx) } + .mapNotNull { ProductSizeJsonParser().parse(it) } } val productType = json.optInt(FIELD_PRODUCT_TYPE) val name = json.optString(FIELD_NAME) @@ -30,7 +30,7 @@ open class ProductJsonParser : VirtusizeJsonParser { productType = productType, name = name, cloudinaryPublicId = cloudinaryPublicId, - storeId = storeId + storeId = storeId, ) } diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductTypeJsonParser.kt b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductTypeJsonParser.kt index 157f79bf..e22db015 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductTypeJsonParser.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/ProductTypeJsonParser.kt @@ -13,9 +13,10 @@ class ProductTypeJsonParser : VirtusizeJsonParser { val name = json.optString(FIELD_NAME) var weights = setOf() json.optJSONObject(FIELD_WEIGHTS)?.let { weightsJsonObject -> - weights = JsonUtils.jsonObjectToMap(weightsJsonObject).map { - Weight(it.key, it.value.toString().toFloatOrNull() ?: 0f) - }.toSet() + weights = + JsonUtils.jsonObjectToMap(weightsJsonObject).map { + Weight(it.key, it.value.toString().toFloatOrNull() ?: 0f) + }.toSet() } val compatibleTypes = JsonUtils.jsonArrayToList(json.optJSONArray(FIELD_COMPATIBLE_WITH)) as? List diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/UserSessionInfoJsonParser.kt b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/UserSessionInfoJsonParser.kt index 45c0821a..b8eb70c7 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/parsers/UserSessionInfoJsonParser.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/parsers/UserSessionInfoJsonParser.kt @@ -7,7 +7,6 @@ import org.json.JSONObject * This class parses a JSONObject to the [UserSessionInfo] object */ class UserSessionInfoJsonParser : VirtusizeJsonParser { - override fun parse(json: JSONObject): UserSessionInfo? { val accessToken = json.optString(FIELD_ID) val authToken = json.optString(FIELD_VS_AUTH) diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/BrandSizing.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/BrandSizing.kt index 09ef4a9e..be083214 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/BrandSizing.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/BrandSizing.kt @@ -7,5 +7,5 @@ package com.virtusize.android.data.remote */ data class BrandSizing( val compare: String, - val itemBrand: Boolean + val itemBrand: Boolean, ) diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/Data.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/Data.kt index 4b1e255b..e2cd7d5d 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/Data.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/Data.kt @@ -16,5 +16,5 @@ data class Data( val productTypeName: String, val storeName: String, val storeId: Int, - val productTypeId: Int + val productTypeId: Int, ) : Parcelable diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/I18nLocalization.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/I18nLocalization.kt index e1fbf395..db5f6ba8 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/I18nLocalization.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/I18nLocalization.kt @@ -19,11 +19,11 @@ data class I18nLocalization( val bodyProfileOneSizeText: String, val sizeComparisonMultiSizeText: String, val bodyProfileMultiSizeText: String, - val defaultNoDataText: String + val defaultNoDataText: String, ) { - enum class TrimType { - ONELINE, MULTIPLELINES + ONELINE, + MULTIPLELINES, } /** @@ -37,15 +37,15 @@ data class I18nLocalization( /** * Gets the product comparison text for a one-size product */ - internal fun getOneSizeProductComparisonText( - sizeComparisonRecommendedSize: SizeComparisonRecommendedSize - ): String { - if (sizeComparisonRecommendedSize.bestFitScore > 84) + internal fun getOneSizeProductComparisonText(sizeComparisonRecommendedSize: SizeComparisonRecommendedSize): String { + if (sizeComparisonRecommendedSize.bestFitScore > 84) { return "$oneSizeCloseTopText ${I18nConstants.BOLD_START_PLACEHOLDER}" + "$oneSizeCloseBottomText${I18nConstants.BOLD_END_PLACEHOLDER}" - if (sizeComparisonRecommendedSize.isStoreProductSmaller == true) + } + if (sizeComparisonRecommendedSize.isStoreProductSmaller == true) { return "$oneSizeSmallerTopText ${I18nConstants.BOLD_START_PLACEHOLDER}" + "$oneSizeSmallerBottomText${I18nConstants.BOLD_END_PLACEHOLDER}" + } return "$oneSizeLargerTopText ${I18nConstants.BOLD_START_PLACEHOLDER}" + "$oneSizeLargerBottomText${I18nConstants.BOLD_END_PLACEHOLDER}" } @@ -53,9 +53,7 @@ data class I18nLocalization( /** * Gets the product comparison text for a multi-size product */ - internal fun getMultiSizeProductComparisonText( - sizeComparisonRecommendedSizeName: String - ): String { + internal fun getMultiSizeProductComparisonText(sizeComparisonRecommendedSizeName: String): String { return "$sizeComparisonMultiSizeText ${I18nConstants.BOLD_START_PLACEHOLDER}" + "$sizeComparisonRecommendedSizeName${I18nConstants.BOLD_END_PLACEHOLDER}" } diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/Measurement.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/Measurement.kt index 77da29a6..3d1a06ad 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/Measurement.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/Measurement.kt @@ -7,5 +7,5 @@ package com.virtusize.android.data.remote */ data class Measurement( val name: String, - val millimeter: Int + val millimeter: Int, ) 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 d2588bec..2f2530e0 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 @@ -28,7 +28,7 @@ data class Product( var isFavorite: Boolean? = null, val storeId: Int, var storeProductMeta: StoreProductMeta? = null, - var clientProductImageURL: String? = null + var clientProductImageURL: String? = null, ) { /** * Gets the InPage recommendation text based on the product info @@ -38,20 +38,22 @@ data class Product( fun getRecommendationText( i18nLocalization: I18nLocalization, sizeComparisonRecommendedSize: SizeComparisonRecommendedSize?, - bodyProfileRecommendedSizeName: String? + bodyProfileRecommendedSizeName: String?, ): String { return when { isAccessory() -> accessoryText(i18nLocalization, sizeComparisonRecommendedSize) - sizes.size == 1 -> oneSizeText( - i18nLocalization, - sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) - else -> multiSizeText( - i18nLocalization, - sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + sizes.size == 1 -> + oneSizeText( + i18nLocalization, + sizeComparisonRecommendedSize, + bodyProfileRecommendedSizeName, + ) + else -> + multiSizeText( + i18nLocalization, + sizeComparisonRecommendedSize, + bodyProfileRecommendedSizeName, + ) } } @@ -68,12 +70,13 @@ data class Product( */ private fun accessoryText( i18nLocalization: I18nLocalization, - sizeComparisonRecommendedSize: SizeComparisonRecommendedSize? + sizeComparisonRecommendedSize: SizeComparisonRecommendedSize?, ): String { - return if (sizeComparisonRecommendedSize?.bestStoreProductSize?.name != null) + return if (sizeComparisonRecommendedSize?.bestStoreProductSize?.name != null) { i18nLocalization.getHasProductAccessoryText() - else + } else { i18nLocalization.defaultAccessoryText + } } /** @@ -82,7 +85,7 @@ data class Product( private fun oneSizeText( i18nLocalization: I18nLocalization, sizeComparisonRecommendedSize: SizeComparisonRecommendedSize?, - bodyProfileRecommendedSizeName: String? + bodyProfileRecommendedSizeName: String?, ): String { sizeComparisonRecommendedSize?.let { return i18nLocalization.getOneSizeProductComparisonText(it) @@ -99,7 +102,7 @@ data class Product( private fun multiSizeText( i18nLocalization: I18nLocalization, sizeComparisonRecommendedSize: SizeComparisonRecommendedSize?, - bodyProfileRecommendedSizeName: String? + bodyProfileRecommendedSizeName: String?, ): String { sizeComparisonRecommendedSize?.bestStoreProductSize?.name?.let { return i18nLocalization.getMultiSizeProductComparisonText(it) diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductCheck.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductCheck.kt index 800e40ac..fba49a1e 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductCheck.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductCheck.kt @@ -13,5 +13,5 @@ data class ProductCheck( val productId: String, val name: String, // the JSON response as a String (For the Flutter SDK) - val jsonString: String? + val jsonString: String?, ) : Parcelable diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductSize.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductSize.kt index 8a796843..585193b1 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductSize.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductSize.kt @@ -8,5 +8,5 @@ package com.virtusize.android.data.remote */ data class ProductSize( val name: String, - val measurements: Set + val measurements: Set, ) diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductType.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductType.kt index 6510dbfd..5cd42511 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductType.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/ProductType.kt @@ -10,5 +10,5 @@ data class ProductType( val id: Int, val name: String, val weights: Set, - val compatibleTypes: List + val compatibleTypes: List, ) diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/StoreProductAdditionalInfo.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/StoreProductAdditionalInfo.kt index c202e46a..a339c197 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/StoreProductAdditionalInfo.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/StoreProductAdditionalInfo.kt @@ -19,5 +19,5 @@ data class StoreProductAdditionalInfo( val modelInfo: Map?, val fit: String?, val style: String?, - val brandSizing: BrandSizing? + val brandSizing: BrandSizing?, ) diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/StoreProductMeta.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/StoreProductMeta.kt index dd9d8ef4..abc32137 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/StoreProductMeta.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/StoreProductMeta.kt @@ -12,5 +12,5 @@ data class StoreProductMeta( val id: Int, val additionalInfo: StoreProductAdditionalInfo?, val brand: String, - val gender: String? + val gender: String?, ) 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 061600c4..79105a6e 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 @@ -13,5 +13,5 @@ data class UserBodyProfile( val age: Int, val height: Int, val weight: String, - val bodyData: Set + val bodyData: Set, ) diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/UserSessionInfo.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/UserSessionInfo.kt index 5734266e..f8306b8e 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/UserSessionInfo.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/UserSessionInfo.kt @@ -11,5 +11,5 @@ data class UserSessionInfo( val accessToken: String, internal val bid: String?, val authToken: String, - val userSessionResponse: String + val userSessionResponse: String, ) diff --git a/virtusize-core/src/main/java/com/virtusize/android/data/remote/Weight.kt b/virtusize-core/src/main/java/com/virtusize/android/data/remote/Weight.kt index cf22312c..4462e855 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/data/remote/Weight.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/data/remote/Weight.kt @@ -7,5 +7,5 @@ package com.virtusize.android.data.remote */ data class Weight( val factor: String, - val value: Float + val value: Float, ) 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 360a0035..68240756 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 @@ -25,7 +25,7 @@ import com.virtusize.android.data.remote.UserBodyProfile enum class HttpMethod { GET, POST, - DELETE + DELETE, } /** @@ -39,7 +39,7 @@ data class ApiRequest( val url: String, val method: HttpMethod, val params: Map = mutableMapOf(), - val authorization: Boolean = false + val authorization: Boolean = false, ) /** @@ -62,7 +62,7 @@ object VirtusizeApi { fun init( env: VirtusizeEnvironment, key: String, - userId: String + userId: String, ) { environment = env apiKey = key @@ -98,10 +98,11 @@ object VirtusizeApi { * @return the Virtusize web view URL as String */ fun virtusizeWebViewURL(): String { - val urlBuilder = Uri.parse( - environment.virtusizeUrl() + VirtusizeEndpoint.VirtusizeWebView.getPath(environment) - ) - .buildUpon() + val urlBuilder = + Uri.parse( + environment.virtusizeUrl() + VirtusizeEndpoint.VirtusizeWebView.getPath(environment), + ) + .buildUpon() return urlBuilder.build().toString() } @@ -111,13 +112,14 @@ object VirtusizeApi { * @return ApiRequest */ fun sendProductImageToBackend(product: VirtusizeProduct): ApiRequest { - val url = Uri.parse( - environment.defaultApiUrl() + - VirtusizeEndpoint.ProductMetaDataHints.getPath() - ) - .buildUpon() - .build() - .toString() + val url = + Uri.parse( + environment.defaultApiUrl() + + VirtusizeEndpoint.ProductMetaDataHints.getPath(), + ) + .buildUpon() + .build() + .toString() val params = mutableMapOf() product.productCheckData?.data?.storeId?.let { params["store_id"] = it.toString() @@ -145,19 +147,20 @@ object VirtusizeApi { productCheck: ProductCheck?, deviceOrientation: String, screenResolution: String, - versionName: String + versionName: String, ): ApiRequest { - val url = Uri.parse(environment.eventApiUrl()) - .buildUpon() - .build() - .toString() + val url = + Uri.parse(environment.eventApiUrl()) + .buildUpon() + .build() + .toString() val params = buildEventPayload( virtusizeEvent, productCheck, deviceOrientation, screenResolution, - versionName + versionName, ) return ApiRequest(url, HttpMethod.POST, params) } @@ -179,7 +182,7 @@ object VirtusizeApi { productCheck: ProductCheck?, orientation: String, resolution: String, - versionName: String + versionName: String, ): MutableMap { val params = mutableMapOf() params["name"] = virtusizeEvent.name @@ -224,10 +227,11 @@ object VirtusizeApi { * @see ApiRequest */ fun sendOrder(order: VirtusizeOrder): ApiRequest { - val url = Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.Orders.getPath()) - .buildUpon() - .build() - .toString() + val url = + Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.Orders.getPath()) + .buildUpon() + .build() + .toString() return ApiRequest(url, HttpMethod.POST, order.paramsToMap(apiKey, userId).toMutableMap()) } @@ -237,15 +241,16 @@ object VirtusizeApi { * @see ApiRequest */ fun getStoreInfo(): ApiRequest { - val url = Uri.parse( - environment.defaultApiUrl() + - VirtusizeEndpoint.StoreViewApiKey.getPath() + - apiKey - ) - .buildUpon() - .appendQueryParameter("format", "json") - .build() - .toString() + val url = + Uri.parse( + environment.defaultApiUrl() + + VirtusizeEndpoint.StoreViewApiKey.getPath() + + apiKey, + ) + .buildUpon() + .appendQueryParameter("format", "json") + .build() + .toString() return ApiRequest(url, HttpMethod.GET) } @@ -255,15 +260,16 @@ object VirtusizeApi { * @see ApiRequest */ fun getStoreProductInfo(productId: String): ApiRequest { - val url = Uri.parse( - environment.defaultApiUrl() + - VirtusizeEndpoint.StoreProducts.getPath() + - productId - ) - .buildUpon() - .appendQueryParameter("format", "json") - .build() - .toString() + val url = + Uri.parse( + environment.defaultApiUrl() + + VirtusizeEndpoint.StoreProducts.getPath() + + productId, + ) + .buildUpon() + .appendQueryParameter("format", "json") + .build() + .toString() return ApiRequest(url, HttpMethod.GET) } @@ -272,10 +278,11 @@ object VirtusizeApi { * @see ApiRequest */ fun getProductTypes(): ApiRequest { - val url = Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.ProductType.getPath()) - .buildUpon() - .build() - .toString() + val url = + Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.ProductType.getPath()) + .buildUpon() + .build() + .toString() return ApiRequest(url, HttpMethod.GET) } @@ -284,18 +291,20 @@ object VirtusizeApi { * @see ApiRequest */ fun getI18n(language: VirtusizeLanguage): ApiRequest { - val url = Uri.parse(I18N_URL + VirtusizeEndpoint.I18N.getPath() + language.value) - .buildUpon() - .build() - .toString() + val url = + Uri.parse(I18N_URL + VirtusizeEndpoint.I18N.getPath() + language.value) + .buildUpon() + .build() + .toString() return ApiRequest(url, HttpMethod.GET) } fun getSessions(): ApiRequest { - val url = Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.Sessions.getPath()) - .buildUpon() - .build() - .toString() + val url = + Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.Sessions.getPath()) + .buildUpon() + .build() + .toString() return ApiRequest(url, HttpMethod.POST) } @@ -304,10 +313,11 @@ object VirtusizeApi { * @see ApiRequest */ fun deleteUser(): ApiRequest { - val url = Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.User.getPath()) - .buildUpon() - .build() - .toString() + val url = + Uri.parse(environment.defaultApiUrl() + VirtusizeEndpoint.User.getPath()) + .buildUpon() + .build() + .toString() return ApiRequest(url, HttpMethod.DELETE) } @@ -316,12 +326,13 @@ object VirtusizeApi { * @see ApiRequest */ fun getUserProducts(): ApiRequest { - val url = Uri.parse( - environment.defaultApiUrl() + VirtusizeEndpoint.UserProducts.getPath() - ) - .buildUpon() - .build() - .toString() + val url = + Uri.parse( + environment.defaultApiUrl() + VirtusizeEndpoint.UserProducts.getPath(), + ) + .buildUpon() + .build() + .toString() return ApiRequest(url, HttpMethod.GET, authorization = true) } @@ -330,12 +341,13 @@ object VirtusizeApi { * @see ApiRequest */ fun getUserBodyProfile(): ApiRequest { - val url = Uri.parse( - environment.defaultApiUrl() + VirtusizeEndpoint.UserBodyMeasurements.getPath() - ) - .buildUpon() - .build() - .toString() + val url = + Uri.parse( + environment.defaultApiUrl() + VirtusizeEndpoint.UserBodyMeasurements.getPath(), + ) + .buildUpon() + .build() + .toString() return ApiRequest(url, HttpMethod.GET, authorization = true) } @@ -349,7 +361,7 @@ object VirtusizeApi { fun getSize( productTypes: List, storeProduct: Product, - userBodyProfile: UserBodyProfile + userBodyProfile: UserBodyProfile, ): ApiRequest { val bodyProfileRecommendedSizeParams = BodyProfileRecommendedSizeParams(productTypes, storeProduct, userBodyProfile) diff --git a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiResponse.kt b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiResponse.kt index c63bd08b..91ad5ce3 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiResponse.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiResponse.kt @@ -17,6 +17,7 @@ sealed class VirtusizeApiResponse { // The successful response with data of data class Success(val data: T) : VirtusizeApiResponse() + // The error response with an error of [VirtusizeError] data class Error(val error: VirtusizeError) : VirtusizeApiResponse() diff --git a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiTask.kt b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiTask.kt index 3ce015af..db4ca8a9 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiTask.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/network/VirtusizeApiTask.kt @@ -27,9 +27,8 @@ import javax.net.ssl.HttpsURLConnection class VirtusizeApiTask( private var urlConnection: HttpsURLConnection?, private var sharedPreferencesHelper: SharedPreferencesHelper, - private var messageHandler: VirtusizeMessageHandler? + private var messageHandler: VirtusizeMessageHandler?, ) { - companion object { // The read timeout to use for all the requests, which is 80 seconds private val READ_TIMEOUT = TimeUnit.SECONDS.toMillis(80).toInt() @@ -69,47 +68,48 @@ class VirtusizeApiTask( try { if (urlConnection == null) { val url = URL(apiRequest.url + getQueryString(apiRequest)) - urlConnection = (url.openConnection() as HttpsURLConnection).apply { - readTimeout = READ_TIMEOUT - connectTimeout = CONNECT_TIMEOUT - requestMethod = apiRequest.method.name - - setRequestProperty( - HEADER_BROWSER_ID, - sharedPreferencesHelper.getBrowserId() - ) + urlConnection = + (url.openConnection() as HttpsURLConnection).apply { + readTimeout = READ_TIMEOUT + connectTimeout = CONNECT_TIMEOUT + requestMethod = apiRequest.method.name - // Set the access token in the header if the request needs authentication - if (apiRequest.authorization) { setRequestProperty( - HEADER_AUTHORIZATION, - "Token ${sharedPreferencesHelper.getAccessToken()}" + HEADER_BROWSER_ID, + sharedPreferencesHelper.getBrowserId(), ) - } - // Send the POST request - if (apiRequest.method == HttpMethod.POST) { - doOutput = true - setRequestProperty(HEADER_CONTENT_TYPE, "application/json") + // Set the access token in the header if the request needs authentication + if (apiRequest.authorization) { + setRequestProperty( + HEADER_AUTHORIZATION, + "Token ${sharedPreferencesHelper.getAccessToken()}", + ) + } + + // Send the POST request + if (apiRequest.method == HttpMethod.POST) { + doOutput = true + setRequestProperty(HEADER_CONTENT_TYPE, "application/json") - // Set up the request header for the sessions API - if (apiRequest.url.contains(VirtusizeEndpoint.Sessions.getPath())) { - sharedPreferencesHelper.getAuthToken()?.let { - setRequestProperty(HEADER_AUTH, it) - setRequestProperty(HEADER_COOKIE, "") + // Set up the request header for the sessions API + if (apiRequest.url.contains(VirtusizeEndpoint.Sessions.getPath())) { + sharedPreferencesHelper.getAuthToken()?.let { + setRequestProperty(HEADER_AUTH, it) + setRequestProperty(HEADER_COOKIE, "") + } } - } - // Write the byte array of the request body to the output stream - if (apiRequest.params.isNotEmpty()) { - val outStream = DataOutputStream(outputStream) - outStream.write( - JSONObject(apiRequest.params).toString().toByteArray() - ) - outStream.close() + // Write the byte array of the request body to the output stream + if (apiRequest.params.isNotEmpty()) { + val outStream = DataOutputStream(outputStream) + outStream.write( + JSONObject(apiRequest.params).toString().toByteArray(), + ) + outStream.close() + } } } - } } when { @@ -118,16 +118,17 @@ class VirtusizeApiTask( inputStream = urlConnection.inputStream return try { val inputStreamString = readInputStreamAsString(inputStream) - val response = parseInputStreamStringToObject( - apiRequest.url, - inputStreamString - ) + val response = + parseInputStreamStringToObject( + apiRequest.url, + inputStreamString, + ) VirtusizeApiResponse.Success(response) as VirtusizeApiResponse } catch (e: JSONException) { VirtusizeApiResponse.Error( VirtusizeErrorType.JsonParsingError.virtusizeError( - extraMessage = "${apiRequest.url} ${e.localizedMessage}" - ) + extraMessage = "${apiRequest.url} ${e.localizedMessage}", + ), ) } } @@ -136,40 +137,41 @@ class VirtusizeApiTask( errorStream = urlConnection.errorStream val errorStreamString = readInputStreamAsString(errorStream) val response = parseErrorStreamStringToObject(errorStreamString) - val error = when (urlConnection.responseCode) { - HttpURLConnection.HTTP_FORBIDDEN -> { - // If the API key is empty or invalid - VirtusizeErrorType.ApiKeyNullOrInvalid.virtusizeError() - } + val error = + when (urlConnection.responseCode) { + HttpURLConnection.HTTP_FORBIDDEN -> { + // If the API key is empty or invalid + VirtusizeErrorType.ApiKeyNullOrInvalid.virtusizeError() + } - HttpURLConnection.HTTP_NOT_FOUND -> { - // If the product cannot be found in the Virtusize Server - if (response is ProductCheck) { - return VirtusizeApiResponse.Error( - VirtusizeErrorType.UnParsedProduct.virtusizeError( - extraMessage = response.productId + HttpURLConnection.HTTP_NOT_FOUND -> { + // If the product cannot be found in the Virtusize Server + if (response is ProductCheck) { + return VirtusizeApiResponse.Error( + VirtusizeErrorType.UnParsedProduct.virtusizeError( + extraMessage = response.productId, + ), ) + } + VirtusizeErrorType.APIError.virtusizeError( + urlConnection.responseCode, + getAPIErrorMessage( + urlConnection.url?.path, + response ?: urlConnection.responseMessage, + ), ) } - VirtusizeErrorType.APIError.virtusizeError( - urlConnection.responseCode, - getAPIErrorMessage( - urlConnection.url?.path, - response ?: urlConnection.responseMessage - ) - ) - } - else -> { - VirtusizeErrorType.APIError.virtusizeError( - urlConnection.responseCode, - getAPIErrorMessage( - urlConnection.url?.path, - response ?: urlConnection.responseMessage + else -> { + VirtusizeErrorType.APIError.virtusizeError( + urlConnection.responseCode, + getAPIErrorMessage( + urlConnection.url?.path, + response ?: urlConnection.responseMessage, + ), ) - ) + } } - } return VirtusizeApiResponse.Error(error) } @@ -178,19 +180,20 @@ class VirtusizeApiTask( urlConnection.responseCode, getAPIErrorMessage( urlConnection.url?.path, - urlConnection.responseMessage - ) - ) + urlConnection.responseMessage, + ), + ), ) } } catch (e: IOException) { return VirtusizeApiResponse.Error( VirtusizeErrorType.APIError.virtusizeError( - extraMessage = getAPIErrorMessage( - urlConnection?.url?.path, - e.localizedMessage - ) - ) + extraMessage = + getAPIErrorMessage( + urlConnection?.url?.path, + e.localizedMessage, + ), + ), ) } finally { urlConnection?.disconnect() @@ -208,9 +211,10 @@ class VirtusizeApiTask( var queryString = "" if (apiRequest.method == HttpMethod.GET) { if (apiRequest.params.isNotEmpty()) { - queryString += "?" + apiRequest.params - .map { paramMapEntry -> "${paramMapEntry.key}=${paramMapEntry.value}" } - .joinToString("&") + queryString += "?" + + apiRequest.params + .map { paramMapEntry -> "${paramMapEntry.key}=${paramMapEntry.value}" } + .joinToString("&") } } return queryString @@ -224,7 +228,7 @@ class VirtusizeApiTask( */ private fun parseInputStreamStringToObject( apiRequestUrl: String? = null, - inputStreamString: String? = null + inputStreamString: String? = null, ): Any? { var result: Any? = null if (inputStreamString != null) { @@ -233,8 +237,8 @@ class VirtusizeApiTask( } catch (e: JSONException) { messageHandler?.onError( VirtusizeErrorType.JsonParsingError.virtusizeError( - extraMessage = e.localizedMessage - ) + extraMessage = e.localizedMessage, + ), ) } } @@ -246,16 +250,15 @@ class VirtusizeApiTask( * @param errorStreamString the string of the error stream * @return either an object that contains the content of the string of the input stream, the string of the error stream, or null */ - private fun parseErrorStreamStringToObject( - errorStreamString: String? = null - ): Any? { + private fun parseErrorStreamStringToObject(errorStreamString: String? = null): Any? { var result: Any? = null if (errorStreamString != null) { - result = try { - parseStringToObject(streamString = errorStreamString) ?: errorStreamString - } catch (e: JSONException) { - errorStreamString - } + result = + try { + parseStringToObject(streamString = errorStreamString) ?: errorStreamString + } catch (e: JSONException) { + errorStreamString + } } return result } @@ -268,19 +271,20 @@ class VirtusizeApiTask( */ private fun parseStringToObject( apiRequestUrl: String? = null, - streamString: String + streamString: String, ): Any? { var result: Any? = null jsonParser?.let { jsonParser -> - result = if (apiRequestUrl != null && responseIsJsonArray(apiRequestUrl)) { - val jsonArray = JSONArray(streamString) - (0 until jsonArray.length()) - .map { idx -> jsonArray.getJSONObject(idx) } - .mapNotNull { jsonParser.parse(it) } - } else { - val jsonObject = JSONObject(streamString) - jsonParser.parse(jsonObject) - } + result = + if (apiRequestUrl != null && responseIsJsonArray(apiRequestUrl)) { + val jsonArray = JSONArray(streamString) + (0 until jsonArray.length()) + .map { idx -> jsonArray.getJSONObject(idx) } + .mapNotNull { jsonParser.parse(it) } + } else { + val jsonObject = JSONObject(streamString) + jsonParser.parse(jsonObject) + } } return result } @@ -291,9 +295,10 @@ class VirtusizeApiTask( * @return the boolean value to tell whether the response of the apiRequestUrl is a JSON array. */ private fun responseIsJsonArray(apiRequestUrl: String): Boolean { - return apiRequestUrl.contains(VirtusizeEndpoint.ProductType.getPath()) || apiRequestUrl.contains( - VirtusizeEndpoint.UserProducts.getPath() - ) || apiRequestUrl.contains(VirtusizeEndpoint.GetSize.getPath()) + return apiRequestUrl.contains(VirtusizeEndpoint.ProductType.getPath()) || + apiRequestUrl.contains( + VirtusizeEndpoint.UserProducts.getPath(), + ) || apiRequestUrl.contains(VirtusizeEndpoint.GetSize.getPath()) } /** @@ -312,7 +317,10 @@ class VirtusizeApiTask( * @param response the response from an API request * @return the message with the info of the request's path and response */ - private fun getAPIErrorMessage(requestPath: String?, response: Any?): String { + private fun getAPIErrorMessage( + requestPath: String?, + response: Any?, + ): String { return "$requestPath - ${response?.toString()}" } } 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 8ce68142..99d4a12c 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 @@ -18,7 +18,7 @@ enum class VirtusizeEndpoint { User, UserProducts, UserBodyMeasurements, - I18N + I18N, } /** @@ -37,10 +37,11 @@ fun VirtusizeEndpoint.getPath(env: VirtusizeEnvironment? = null): String { "/item" } VirtusizeEndpoint.VirtusizeWebView -> { - val stgPath = when (env) { - VirtusizeEnvironment.TESTING, VirtusizeEnvironment.STAGING -> "staging" - else -> "latest" - } + val stgPath = + when (env) { + VirtusizeEnvironment.TESTING, VirtusizeEnvironment.STAGING -> "staging" + else -> "latest" + } "/a/aoyama/$stgPath/sdk-webview.html" } VirtusizeEndpoint.ProductMetaDataHints -> { diff --git a/virtusize-core/src/main/java/com/virtusize/android/util/Extensions.kt b/virtusize-core/src/main/java/com/virtusize/android/util/Extensions.kt index cf01bfe7..ab87373c 100644 --- a/virtusize-core/src/main/java/com/virtusize/android/util/Extensions.kt +++ b/virtusize-core/src/main/java/com/virtusize/android/util/Extensions.kt @@ -52,9 +52,7 @@ fun Context.getTypefaceByName(fontFileName: String): Typeface? { /** * The String extension function to trim the text from i18n localization */ -fun String.trimI18nText( - trimType: I18nLocalization.TrimType = I18nLocalization.TrimType.ONELINE -): String { +fun String.trimI18nText(trimType: I18nLocalization.TrimType = I18nLocalization.TrimType.ONELINE): String { return when (trimType) { I18nLocalization.TrimType.ONELINE -> replace(I18nConstants.BOLD_START_PLACEHOLDER, "") @@ -80,20 +78,25 @@ inline fun > valueOf(type: String): T? { /** * The View extension function to get the latest size info when the view size gets changed */ -inline fun View.onSizeChanged(crossinline runnable: (Int, Int) -> Unit) = this.apply { - addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> - val rect = Rect(left, top, right, bottom) - val oldRect = Rect(oldLeft, oldTop, oldRight, oldBottom) - if (rect.width() != oldRect.width() || rect.height() != oldRect.height()) { - runnable(rect.width(), rect.height()) +inline fun View.onSizeChanged(crossinline runnable: (Int, Int) -> Unit) = + this.apply { + addOnLayoutChangeListener { _, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> + val rect = Rect(left, top, right, bottom) + val oldRect = Rect(oldLeft, oldTop, oldRight, oldBottom) + if (rect.width() != oldRect.width() || rect.height() != oldRect.height()) { + runnable(rect.width(), rect.height()) + } } } -} /** * The TextView extension function to set the width and height for the right drawable of a Button */ -fun TextView.rightDrawable(@DrawableRes id: Int = 0, width: Float, height: Float) { +fun TextView.rightDrawable( + @DrawableRes id: Int = 0, + width: Float, + height: Float, +) { val drawable = ContextCompat.getDrawable(context, id) drawable?.setBounds(0, 0, width.toInt(), height.toInt()) this.setCompoundDrawables(null, null, drawable, null) @@ -109,20 +112,22 @@ val Int.dpInPx: Int * Float extension function to convert sp to px */ val Float.spToPx: Float - get() = TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_SP, - this, - Resources.getSystem().displayMetrics - ) + get() = + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, + this, + Resources.getSystem().displayMetrics, + ) /* * For the Fit Illustrator web view */ val String.isFitIllustratorURL: Boolean - get() = this.contains("virtusize") && - this.contains("fit-illustrator") && - !this.contains("#") && - !this.contains("oauth") + get() = + this.contains("virtusize") && + this.contains("fit-illustrator") && + !this.contains("#") && + !this.contains("oauth") val WebResourceRequest.isFitIllustratorURL: Boolean get() = this.url.toString().isFitIllustratorURL 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 cf8bbeb3..8f2eece9 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 @@ -9,7 +9,6 @@ import org.junit.Before import org.junit.Test class BodyProfileRecommendedSizeParamsTests { - private var productTypes = mutableListOf() @Before @@ -19,77 +18,80 @@ class BodyProfileRecommendedSizeParamsTests { @Test fun testCreateAdditionalInfoParams_fullInfo_getExpectedRequestBodyString() { - val bodyProfileRecommendedSizeParams = BodyProfileRecommendedSizeParams( - productTypes, - ProductFixtures.storeProduct(), - TestFixtures.userBodyProfile - ) + val bodyProfileRecommendedSizeParams = + BodyProfileRecommendedSizeParams( + productTypes, + ProductFixtures.storeProduct(), + TestFixtures.userBodyProfile, + ) val actualAdditionalInfoParams = bodyProfileRecommendedSizeParams.createAdditionalInfoParams() assertThat(JSONObject(actualAdditionalInfoParams).toString()).isEqualTo( """ - { - "fit": "regular", - "sizes": { - "38": { - "bust": 660, - "sleeve": 845, - "height": 760 - }, - "36": { - "bust": 645, - "sleeve": 825, - "height": 750 - } - }, - "modelInfo": { - "waist": 56, - "bust": 78, - "size": "38", - "hip": 85, - "height": 165 + { + "fit": "regular", + "sizes": { + "38": { + "bust": 660, + "sleeve": 845, + "height": 760 }, - "gender": "female", - "brand": "Virtusize" - } - """.trimIndent().replace("\\s+|[\\n]+".toRegex(), "") + "36": { + "bust": 645, + "sleeve": 825, + "height": 750 + } + }, + "modelInfo": { + "waist": 56, + "bust": 78, + "size": "38", + "hip": 85, + "height": 165 + }, + "gender": "female", + "brand": "Virtusize" + } + """.trimIndent().replace("\\s+|[\\n]+".toRegex(), ""), ) } @Test fun testCreateAdditionalInfoParams_hasEmptyValues_getExpectedRequestBodyString() { - val bodyProfileRecommendedSizeParams = BodyProfileRecommendedSizeParams( - productTypes, - ProductFixtures.storeProduct( - sizeList = mutableListOf(), - brand = "", - modelInfo = null, - gender = null - ), - TestFixtures.userBodyProfile - ) + val bodyProfileRecommendedSizeParams = + BodyProfileRecommendedSizeParams( + productTypes, + ProductFixtures.storeProduct( + sizeList = mutableListOf(), + brand = "", + modelInfo = null, + gender = null, + ), + TestFixtures.userBodyProfile, + ) val actualAdditionalInfoParams = bodyProfileRecommendedSizeParams.createAdditionalInfoParams() assertThat(JSONObject(actualAdditionalInfoParams).toString()).isEqualTo( """ - { - "fit": "regular", - "sizes": {}, - "modelInfo": null, - "gender": "female", - "brand": "" - } - """.trimIndent().replace("\\s+|[\\n]+".toRegex(), "") + { + "fit": "regular", + "sizes": {}, + "modelInfo": null, + "gender": "female", + "brand": "" + } + """.trimIndent().replace("\\s+|[\\n]+".toRegex(), ""), ) } @Test fun testCreateBodyDataParams_getExpectedRequestBodyString() { - val bodyProfileRecommendedSizeParams = BodyProfileRecommendedSizeParams( - productTypes, - ProductFixtures.storeProduct(), - TestFixtures.userBodyProfile - ) + val bodyProfileRecommendedSizeParams = + BodyProfileRecommendedSizeParams( + productTypes, + ProductFixtures.storeProduct(), + TestFixtures.userBodyProfile, + ) val bodyDataParams = bodyProfileRecommendedSizeParams.createBodyDataParams() assertThat(JSONObject(bodyDataParams).toString()).isEqualTo( """ @@ -183,138 +185,162 @@ class BodyProfileRecommendedSizeParamsTests { "predicted": true } } - """.trimIndent().replace("\\s+|[\\n]+".toRegex(), "") + """.trimIndent().replace("\\s+|[\\n]+".toRegex(), ""), ) } @Test fun testCreateItemSizesParams_getExpectedRequestBodyString() { - val bodyProfileRecommendedSizeParams = BodyProfileRecommendedSizeParams( - productTypes, - ProductFixtures.storeProduct(), - TestFixtures.userBodyProfile - ) + val bodyProfileRecommendedSizeParams = + BodyProfileRecommendedSizeParams( + productTypes, + ProductFixtures.storeProduct(), + TestFixtures.userBodyProfile, + ) val itemSizesParams = bodyProfileRecommendedSizeParams.createItemSizesParams() assertThat(JSONObject(itemSizesParams).toString().trimIndent()).isEqualTo( """ - { - "38": { - "bust": 660, - "sleeve": 845, - "height": 760 - }, - "36": { - "bust": 645, - "sleeve": 825, - "height": 750 - } + { + "38": { + "bust": 660, + "sleeve": 845, + "height": 760 + }, + "36": { + "bust": 645, + "sleeve": 825, + "height": 750 } - """.trimIndent().replace("\\s+|[\\n]+".toRegex(), "") + } + """.trimIndent().replace("\\s+|[\\n]+".toRegex(), ""), ) } @Test fun testBodyProfileRecommendedSizeParams_getExpectedRequestBodyString() { - val bodyProfileRecommendedSizeParams = BodyProfileRecommendedSizeParams( - productTypes, - ProductFixtures.storeProduct(), - TestFixtures.userBodyProfile - ) + val bodyProfileRecommendedSizeParams = + BodyProfileRecommendedSizeParams( + productTypes, + ProductFixtures.storeProduct(), + 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( mutableMapOf( - "waistWidth" to mutableMapOf( - "value" to 225, - "predicted" to true - ), - "chest" to mutableMapOf( - "value" to 755, - "predicted" to true - ), - "bustWidth" to mutableMapOf( - "value" to 245, - "predicted" to true - ), - "thigh" to mutableMapOf( - "value" to 480, - "predicted" to true - ), - "shoulderWidth" to mutableMapOf( - "value" to 340, - "predicted" to true - ), - "hipHeight" to mutableMapOf( - "value" to 750, - "predicted" to true - ), - "kneeHeight" to mutableMapOf( - "value" to 395, - "predicted" to true - ), - "neck" to mutableMapOf( - "value" to 300, - "predicted" to true - ), - "waistHeight" to mutableMapOf( - "value" to 920, - "predicted" to true - ), - "hip" to mutableMapOf( - "value" to 830, - "predicted" to true - ), - "armpitHeight" to mutableMapOf( - "value" to 1130, - "predicted" to true - ), - "bicep" to mutableMapOf( - "value" to 220, - "predicted" to true - ), - "inseam" to mutableMapOf( - "value" to 700, - "predicted" to true - ), - "headHeight" to mutableMapOf( - "value" to 215, - "predicted" to true - ), - "hipWidth" to mutableMapOf( - "value" to 300, - "predicted" to true - ), - "sleeve" to mutableMapOf( - "value" to 720, - "predicted" to true - ), - "bust" to mutableMapOf( - "value" to 755, - "predicted" to true - ), - "waist" to mutableMapOf( - "value" to 630, - "predicted" to true - ), - "sleeveLength" to mutableMapOf( - "value" to 520, - "predicted" to true - ), - "rise" to mutableMapOf( - "value" to 215, - "predicted" to true - ), - "shoulder" to mutableMapOf( - "value" to 370, - "predicted" to true - ), - "shoulderHeight" to mutableMapOf( - "value" to 1240, - "predicted" to true - ) - ) + "waistWidth" to + mutableMapOf( + "value" to 225, + "predicted" to true, + ), + "chest" to + mutableMapOf( + "value" to 755, + "predicted" to true, + ), + "bustWidth" to + mutableMapOf( + "value" to 245, + "predicted" to true, + ), + "thigh" to + mutableMapOf( + "value" to 480, + "predicted" to true, + ), + "shoulderWidth" to + mutableMapOf( + "value" to 340, + "predicted" to true, + ), + "hipHeight" to + mutableMapOf( + "value" to 750, + "predicted" to true, + ), + "kneeHeight" to + mutableMapOf( + "value" to 395, + "predicted" to true, + ), + "neck" to + mutableMapOf( + "value" to 300, + "predicted" to true, + ), + "waistHeight" to + mutableMapOf( + "value" to 920, + "predicted" to true, + ), + "hip" to + mutableMapOf( + "value" to 830, + "predicted" to true, + ), + "armpitHeight" to + mutableMapOf( + "value" to 1130, + "predicted" to true, + ), + "bicep" to + mutableMapOf( + "value" to 220, + "predicted" to true, + ), + "inseam" to + mutableMapOf( + "value" to 700, + "predicted" to true, + ), + "headHeight" to + mutableMapOf( + "value" to 215, + "predicted" to true, + ), + "hipWidth" to + mutableMapOf( + "value" to 300, + "predicted" to true, + ), + "sleeve" to + mutableMapOf( + "value" to 720, + "predicted" to true, + ), + "bust" to + mutableMapOf( + "value" to 755, + "predicted" to true, + ), + "waist" to + mutableMapOf( + "value" to 630, + "predicted" to true, + ), + "sleeveLength" to + mutableMapOf( + "value" to 520, + "predicted" to true, + ), + "rise" to + mutableMapOf( + "value" to 215, + "predicted" to true, + ), + "shoulder" to + mutableMapOf( + "value" to 370, + "predicted" to true, + ), + "shoulderHeight" to + mutableMapOf( + "value" to 1240, + "predicted" to true, + ), + ), ) val items = bodyProfileRecommendedSizeParamsMap["items"] as Array> for (item in items) { @@ -322,43 +348,49 @@ class BodyProfileRecommendedSizeParamsTests { assertThat(item["productType"]).isEqualTo("jacket") assertThat(item["itemSizesOrig"]).isEqualTo( mutableMapOf( - "38" to mutableMapOf( - "bust" to 660, - "sleeve" to 845, - "height" to 760 - ), - "36" to mutableMapOf( - "bust" to 645, - "sleeve" to 825, - "height" to 750 - ) - ) - ) - assertThat(item["additionalInfo"]).isEqualTo( - mutableMapOf( - "fit" to "regular", - "sizes" to mutableMapOf( - "38" to mutableMapOf( + "38" to + mutableMapOf( "bust" to 660, "sleeve" to 845, - "height" to 760 + "height" to 760, ), - "36" to mutableMapOf( + "36" to + mutableMapOf( "bust" to 645, "sleeve" to 825, - "height" to 750 - ) - ), + "height" to 750, + ), + ), + ) + assertThat(item["additionalInfo"]).isEqualTo( + mutableMapOf( + "fit" to "regular", + "sizes" to + mutableMapOf( + "38" to + mutableMapOf( + "bust" to 660, + "sleeve" to 845, + "height" to 760, + ), + "36" to + mutableMapOf( + "bust" to 645, + "sleeve" to 825, + "height" to 750, + ), + ), "gender" to "female", "brand" to "Virtusize", - "modelInfo" to mutableMapOf( - "waist" to 56, - "bust" to 78, - "size" to "38", - "hip" to 85, - "height" to 165 - ) - ) + "modelInfo" to + mutableMapOf( + "waist" to 56, + "bust" to 78, + "size" to "38", + "hip" to 85, + "height" to 165, + ), + ), ) } } diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderItemTest.kt b/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderItemTest.kt index 9cbc911b..53db1c8e 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderItemTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderItemTest.kt @@ -4,84 +4,89 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test class VirtusizeOrderItemTest { - @Test fun paramsToMap_shouldReturnExpectedMap() { - val actualMap = VirtusizeOrderItem( - "P001", - "L", - "Large", - "P001_SIZEL_RED", - "http://images.example.com/products/P001/red/image1xl.jpg", - "Red", - "W", - 5000.00, - "JPY", - 1, - "http://example.com/products/P001" - ).paramsToMap() + val actualMap = + VirtusizeOrderItem( + "P001", + "L", + "Large", + "P001_SIZEL_RED", + "http://images.example.com/products/P001/red/image1xl.jpg", + "Red", + "W", + 5000.00, + "JPY", + 1, + "http://example.com/products/P001", + ).paramsToMap() - val expectedMap = mapOf( - "externalProductId" to "P001", - "size" to "L", - "sizeAlias" to "Large", - "variantId" to "P001_SIZEL_RED", - "imageUrl" to "http://images.example.com/products/P001/red/image1xl.jpg", - "color" to "Red", - "gender" to "W", - "unitPrice" to 5000.00, - "currency" to "JPY", - "quantity" to 1, - "url" to "http://example.com/products/P001" - ) + val expectedMap = + mapOf( + "externalProductId" to "P001", + "size" to "L", + "sizeAlias" to "Large", + "variantId" to "P001_SIZEL_RED", + "imageUrl" to "http://images.example.com/products/P001/red/image1xl.jpg", + "color" to "Red", + "gender" to "W", + "unitPrice" to 5000.00, + "currency" to "JPY", + "quantity" to 1, + "url" to "http://example.com/products/P001", + ) assertThat(actualMap).isEqualTo(expectedMap) } @Test fun paramsToMap_whenOptionalParamsAreNotProvided_shouldReturnExpectedMap() { - val actualMap = VirtusizeOrderItem( - "P001", - "L", - imageUrl = "http://images.example.com/products/P001/red/image1xl.jpg", - unitPrice = 5000.00, - currency = "JPY", - url = "http://example.com/products/P001" - ).paramsToMap() + val actualMap = + VirtusizeOrderItem( + "P001", + "L", + imageUrl = "http://images.example.com/products/P001/red/image1xl.jpg", + unitPrice = 5000.00, + currency = "JPY", + url = "http://example.com/products/P001", + ).paramsToMap() - val expectedMap = mapOf( - "externalProductId" to "P001", - "size" to "L", - "imageUrl" to "http://images.example.com/products/P001/red/image1xl.jpg", - "unitPrice" to 5000.00, - "currency" to "JPY", - "quantity" to 1, - "url" to "http://example.com/products/P001" - ) + val expectedMap = + mapOf( + "externalProductId" to "P001", + "size" to "L", + "imageUrl" to "http://images.example.com/products/P001/red/image1xl.jpg", + "unitPrice" to 5000.00, + "currency" to "JPY", + "quantity" to 1, + "url" to "http://example.com/products/P001", + ) assertThat(actualMap).isEqualTo(expectedMap) } @Test fun paramsToMap_whenUnitPriceHasMoreThanTwoDecimals_shouldReturnExpectedRoundedNumber() { - val actualMap = VirtusizeOrderItem( - "P002", - "M", - imageUrl = "http://images.example.com/products/P002/red/image1xl.jpg", - unitPrice = 5000.005234, - currency = "JPY", - url = "http://example.com/products/P002" - ).paramsToMap() + val actualMap = + VirtusizeOrderItem( + "P002", + "M", + imageUrl = "http://images.example.com/products/P002/red/image1xl.jpg", + unitPrice = 5000.005234, + currency = "JPY", + url = "http://example.com/products/P002", + ).paramsToMap() - val expectedMap = mapOf( - "externalProductId" to "P002", - "size" to "M", - "imageUrl" to "http://images.example.com/products/P002/red/image1xl.jpg", - "unitPrice" to 5000.01, - "currency" to "JPY", - "quantity" to 1, - "url" to "http://example.com/products/P002" - ) + val expectedMap = + mapOf( + "externalProductId" to "P002", + "size" to "M", + "imageUrl" to "http://images.example.com/products/P002/red/image1xl.jpg", + "unitPrice" to 5000.01, + "currency" to "JPY", + "quantity" to 1, + "url" to "http://example.com/products/P002", + ) assertThat(actualMap).isEqualTo(expectedMap) } diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderTest.kt b/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderTest.kt index 915b5313..53cb923c 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/local/VirtusizeOrderTest.kt @@ -4,7 +4,6 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test class VirtusizeOrderTest { - @Test fun paramsToMap_withNoOrderItem_shouldReturnExpectedMap() { val actualOrder = VirtusizeOrder("TEST_ORDER") @@ -12,13 +11,14 @@ class VirtusizeOrderTest { val actualMap = actualOrder.paramsToMap("test_apiKey", "userId") - val expectedMap = mapOf( - "apiKey" to "test_apiKey", - "externalOrderId" to "TEST_ORDER", - "externalUserId" to "userId", - "region" to "JP", - "items" to mutableListOf() - ) + val expectedMap = + mapOf( + "apiKey" to "test_apiKey", + "externalOrderId" to "TEST_ORDER", + "externalUserId" to "userId", + "region" to "JP", + "items" to mutableListOf(), + ) assertThat(actualMap).isEqualTo(expectedMap) } @@ -27,33 +27,36 @@ class VirtusizeOrderTest { fun paramsToMap_withTwoOrderItems_shouldReturnExpectedMap() { val actualOrder = VirtusizeOrder("TEST_ORDER") - val item1 = VirtusizeOrderItem( - "P001", - "S", - imageUrl = "http://images.example.com/products/P001/red/image1xl.jpg", - unitPrice = 5000.005234, - currency = "JPY", - url = "http://example.com/products/P001" - ) - - val item2 = VirtusizeOrderItem( - "P002", - "M", - imageUrl = "http://images.example.com/products/P002/red/image1xl.jpg", - unitPrice = 5000.005234, - currency = "JPY", - url = "http://example.com/products/P002" - ) + val item1 = + VirtusizeOrderItem( + "P001", + "S", + imageUrl = "http://images.example.com/products/P001/red/image1xl.jpg", + unitPrice = 5000.005234, + currency = "JPY", + url = "http://example.com/products/P001", + ) + + val item2 = + VirtusizeOrderItem( + "P002", + "M", + imageUrl = "http://images.example.com/products/P002/red/image1xl.jpg", + unitPrice = 5000.005234, + currency = "JPY", + url = "http://example.com/products/P002", + ) actualOrder.items.addAll(mutableListOf(item1, item2)) val actualMap = actualOrder.paramsToMap("test_apiKey", "userId") - val expectedMap = mapOf( - "apiKey" to "test_apiKey", - "externalOrderId" to "TEST_ORDER", - "externalUserId" to "userId", - "items" to mutableListOf(item1.paramsToMap(), item2.paramsToMap()) - ) + val expectedMap = + mapOf( + "apiKey" to "test_apiKey", + "externalOrderId" to "TEST_ORDER", + "externalUserId" to "userId", + "items" to mutableListOf(item1.paramsToMap(), item2.paramsToMap()), + ) assertThat(actualMap).isEqualTo(expectedMap) } diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/DataJsonParserTest.kt b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/DataJsonParserTest.kt index 93d2ef8f..f4d9ad13 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/DataJsonParserTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/DataJsonParserTest.kt @@ -6,20 +6,20 @@ import com.virtusize.android.fixtures.TestFixtures import org.junit.Test class DataJsonParserTest { - @Test fun parse_shouldReturnExpectedObject() { val actualData = DataJsonParser().parse(TestFixtures.PRODUCT_DATA_CHECK_DATA) - val expectedData = Data( - true, - false, - false, - 7110384, - "pants", - "virtusize", - 2, - 5 - ) + val expectedData = + Data( + true, + false, + false, + 7110384, + "pants", + "virtusize", + 2, + 5, + ) assertThat(actualData).isEqualTo(expectedData) } diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/JsonUtilsTest.kt b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/JsonUtilsTest.kt index e94f0f49..e4c0ae0d 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/JsonUtilsTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/JsonUtilsTest.kt @@ -6,25 +6,27 @@ import org.json.JSONObject import org.junit.Test class JsonUtilsTest { - @Test fun optString_whenValuePresent_findsAndReturnsValue() { - val jsonObject = JSONObject() - .put("key", "value") + val jsonObject = + JSONObject() + .put("key", "value") Truth.assertThat(JsonUtils.optString(jsonObject, "key")).isEqualTo("value") } @Test fun optString_whenValueContainsNull_returnsEmptyString() { - val jsonObject = JSONObject() - .put("key", "null") + val jsonObject = + JSONObject() + .put("key", "null") Truth.assertThat(JsonUtils.optString(jsonObject, "key")).isEmpty() } @Test fun optString_whenNameNotPresent_returnsEmptyString() { - val jsonObject = JSONObject() - .put("key", "value") + val jsonObject = + JSONObject() + .put("key", "value") Truth.assertThat(JsonUtils.optString(jsonObject, "notpresent")).isEmpty() } @@ -35,13 +37,14 @@ class JsonUtilsTest { @Test fun jsonObjectToMap_forSimpleJSONObject_returnsExpectedMap() { - val expectedMap = mapOf( - "key" to "value", - "boolkey" to true, - "numkey" to 123, - "nullkey" to JSONObject.NULL, - "floatkey" to 1234.54 - ) + val expectedMap = + mapOf( + "key" to "value", + "boolkey" to true, + "numkey" to 123, + "nullkey" to JSONObject.NULL, + "floatkey" to 1234.54, + ) val mappedObject = JsonUtils.jsonObjectToMap(SIMPLE_JSON) Truth.assertThat(expectedMap).isEqualTo(mappedObject) @@ -49,20 +52,24 @@ class JsonUtilsTest { @Test fun jsonObjectToMap_forNestedJSONObject_returnsExpectedMap() { - val expectedMap = mapOf( - "key_one" to mapOf( - "key_one_one" to mapOf( - "key_one_one_number" to 123, - "key_one_one_string" to "hello" - ), - "key_two_one" to false - ), - "key_two" to mapOf( - "key_two_one" to "world", - "key_two_two" to emptyMap() - ), - "key_three" to JSONObject.NULL - ) + val expectedMap = + mapOf( + "key_one" to + mapOf( + "key_one_one" to + mapOf( + "key_one_one_number" to 123, + "key_one_one_string" to "hello", + ), + "key_two_one" to false, + ), + "key_two" to + mapOf( + "key_two_one" to "world", + "key_two_two" to emptyMap(), + ), + "key_three" to JSONObject.NULL, + ) val mappedObject = JsonUtils.jsonObjectToMap(NESTED_JSON) Truth.assertThat(expectedMap).isEqualTo(mappedObject) @@ -70,21 +77,24 @@ class JsonUtilsTest { @Test fun jsonObjectToMap_forNestedJSONObjectWithMixedArrays_returnsExpectedMap() { - val items = listOf( - mapOf("id" to 2020324), - mapOf("id" to "stringOrderId"), - "a string item", - 323, - listOf(1, 22.2, "a", true), - listOf(mapOf("something" to "deep")) - ) - val expectedMap = mapOf( - "expired" to false, - "order" to mapOf( - "items" to items, - "timestamp" to "time" + val items = + listOf( + mapOf("id" to 2020324), + mapOf("id" to "stringOrderId"), + "a string item", + 323, + listOf(1, 22.2, "a", true), + listOf(mapOf("something" to "deep")), + ) + val expectedMap = + mapOf( + "expired" to false, + "order" to + mapOf( + "items" to items, + "timestamp" to "time", + ), ) - ) val convertedMap = JsonUtils.jsonObjectToMap(NESTED_JSON_MIXED_ARRAY) Truth.assertThat(expectedMap).isEqualTo(convertedMap) @@ -103,66 +113,69 @@ class JsonUtilsTest { } private companion object { + private val SIMPLE_JSON = + JSONObject( + """ + { + "key": "value", + "boolkey": true, + "numkey": 123, + "nullkey": null, + "floatkey": 1234.54 + } + """.trimIndent(), + ) - private val SIMPLE_JSON = JSONObject( - """ - { - "key": "value", - "boolkey": true, - "numkey": 123, - "nullkey": null, - "floatkey": 1234.54 - } - """.trimIndent() - ) - - private val NESTED_JSON = JSONObject( - """ - { - "key_one": { - "key_one_one": { - "key_one_one_number": 123, - "key_one_one_string": "hello" - }, - "key_two_one": false - }, - "key_two": { - "key_two_one": "world", - "key_two_two": {} - }, - "key_three": null - } - """.trimIndent() - ) - - private val SIMPLE_JSON_ARRAY = JSONArray( - """ - [1, 22.2, 3, "a", true, "cde"] - """.trimIndent() - ) - - private val NESTED_JSON_MIXED_ARRAY = JSONObject( - """ - { - "order": { - "items": [{ - "id": 2020324 + private val NESTED_JSON = + JSONObject( + """ + { + "key_one": { + "key_one_one": { + "key_one_one_number": 123, + "key_one_one_string": "hello" }, - { - "id": "stringOrderId" - }, - "a string item", - 323, - [1, 22.2, "a", true], - [{ - "something": "deep" - }] - ], - "timestamp": "time" - }, - "expired": false - } - """.trimIndent() - ) + "key_two_one": false + }, + "key_two": { + "key_two_one": "world", + "key_two_two": {} + }, + "key_three": null + } + """.trimIndent(), + ) + + private val SIMPLE_JSON_ARRAY = + JSONArray( + """ + [1, 22.2, 3, "a", true, "cde"] + """.trimIndent(), + ) + + private val NESTED_JSON_MIXED_ARRAY = + JSONObject( + """ + { + "order": { + "items": [{ + "id": 2020324 + }, + { + "id": "stringOrderId" + }, + "a string item", + 323, + [1, 22.2, "a", true], + [{ + "something": "deep" + }] + ], + "timestamp": "time" + }, + "expired": false + } + """.trimIndent(), + ) } } diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/ProductCheckJsonParserTest.kt b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/ProductCheckJsonParserTest.kt index 65fd0d39..d16c4207 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/ProductCheckJsonParserTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/ProductCheckJsonParserTest.kt @@ -7,27 +7,28 @@ import com.virtusize.android.fixtures.TestFixtures import org.junit.Test class ProductCheckJsonParserTest { - @Test fun parse_shouldReturnExpectedObject() { val actualProductCheck = ProductCheckJsonParser().parse(TestFixtures.PRODUCT_DATA_CHECK) - val expectedData = Data( - true, - false, - true, - 7110384, - "pants", - "virtusize", - 2, - 5 - ) - val expectedProductCheck = ProductCheck( - expectedData, - "694", - "backend-checked-product", - TestFixtures.PRODUCT_DATA_CHECK.toString() - ) + val expectedData = + Data( + true, + false, + true, + 7110384, + "pants", + "virtusize", + 2, + 5, + ) + val expectedProductCheck = + ProductCheck( + expectedData, + "694", + "backend-checked-product", + TestFixtures.PRODUCT_DATA_CHECK.toString(), + ) assertThat(actualProductCheck).isEqualTo(expectedProductCheck) } diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/ProductTypeJsonParserTest.kt b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/ProductTypeJsonParserTest.kt index decbec1d..11168057 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/ProductTypeJsonParserTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/ProductTypeJsonParserTest.kt @@ -8,21 +8,21 @@ import com.virtusize.android.fixtures.TestFixtures import org.junit.Test class ProductTypeJsonParserTest { - @Test fun parse_validJsonData_shouldReturnExpectedObject() { val actualProductType = ProductTypeJsonParser().parse(ProductFixtures.PRODUCT_TYPE_JSON_OBJECT) - val expectedProductType = ProductType( - 1, - "dress", - mutableSetOf( - Weight("bust", 1f), - Weight("waist", 1f), - Weight("height", 0.25f) - ), - mutableListOf(1, 16) - ) + val expectedProductType = + ProductType( + 1, + "dress", + mutableSetOf( + Weight("bust", 1f), + Weight("waist", 1f), + Weight("height", 0.25f), + ), + mutableListOf(1, 16), + ) assertThat(actualProductType).isEqualTo(expectedProductType) } diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/UserBodyProfileJsonParserTest.kt b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/UserBodyProfileJsonParserTest.kt index e996248f..c88f9275 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/UserBodyProfileJsonParserTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/UserBodyProfileJsonParserTest.kt @@ -5,7 +5,6 @@ import com.virtusize.android.fixtures.TestFixtures import org.junit.Test class UserBodyProfileJsonParserTest { - @Test fun test_parseValidUserBodyResponse_returnExpectedUserBodyProfile() { val actualUserBodyProfile = diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/UserProductsJsonParserTest.kt b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/UserProductsJsonParserTest.kt index 109f7e74..a62ec1a8 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/parsers/UserProductsJsonParserTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/parsers/UserProductsJsonParserTest.kt @@ -9,32 +9,32 @@ import com.virtusize.android.fixtures.TestFixtures import org.junit.Test class UserProductsJsonParserTest { - @Test fun parse_validUserProduct_returnExpectedUserProduct() { val actualUserProduct = UserProductJsonParser().parse(ProductFixtures.USER_PRODUCT_ONE_JSON_OBJECT) - val expectedUserProduct = Product( - 123456, - mutableListOf( - ProductSize( - "S", - mutableSetOf( - Measurement("height", 1000), - Measurement("bust", 400), - Measurement("waist", 340) - ) - ) - ), - null, - 11, - "Test Womenswear Strapless Dress", - "", - false, - 0, - null - ) + val expectedUserProduct = + Product( + 123456, + mutableListOf( + ProductSize( + "S", + mutableSetOf( + Measurement("height", 1000), + Measurement("bust", 400), + Measurement("waist", 340), + ), + ), + ), + null, + 11, + "Test Womenswear Strapless Dress", + "", + false, + 0, + null, + ) assertThat(actualUserProduct).isEqualTo(expectedUserProduct) } diff --git a/virtusize-core/src/test/java/com/virtusize/android/data/remote/ProductTest.kt b/virtusize-core/src/test/java/com/virtusize/android/data/remote/ProductTest.kt index c64b5e48..7e5aafea 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/data/remote/ProductTest.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/data/remote/ProductTest.kt @@ -16,7 +16,6 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.Q]) class ProductTest { - private val context: Context = ApplicationProvider.getApplicationContext() private lateinit var i18nLocalization: I18nLocalization @@ -25,21 +24,22 @@ class ProductTest { @Before fun setup() { - i18nLocalization = I18nLocalization( - context.getString(R.string.inpage_default_accessory_text), - context.getString(R.string.inpage_has_product_top_text), - context.getString(R.string.inpage_has_product_bottom_text), - context.getString(R.string.inpage_one_size_close_top_text), - context.getString(R.string.inpage_one_size_smaller_top_text), - context.getString(R.string.inpage_one_size_larger_top_text), - context.getString(R.string.inpage_one_size_close_bottom_text), - context.getString(R.string.inpage_one_size_smaller_bottom_text), - context.getString(R.string.inpage_one_size_larger_bottom_text), - context.getString(R.string.inpage_one_size_body_profile_text), - context.getString(R.string.inpage_multi_size_comparison_text), - context.getString(R.string.inpage_multi_size_body_profile_text), - context.getString(R.string.inpage_no_data_text) - ) + i18nLocalization = + I18nLocalization( + context.getString(R.string.inpage_default_accessory_text), + context.getString(R.string.inpage_has_product_top_text), + context.getString(R.string.inpage_has_product_bottom_text), + context.getString(R.string.inpage_one_size_close_top_text), + context.getString(R.string.inpage_one_size_smaller_top_text), + context.getString(R.string.inpage_one_size_larger_top_text), + context.getString(R.string.inpage_one_size_close_bottom_text), + context.getString(R.string.inpage_one_size_smaller_bottom_text), + context.getString(R.string.inpage_one_size_larger_bottom_text), + context.getString(R.string.inpage_one_size_body_profile_text), + context.getString(R.string.inpage_multi_size_comparison_text), + context.getString(R.string.inpage_multi_size_body_profile_text), + context.getString(R.string.inpage_no_data_text), + ) } @Test @@ -49,29 +49,29 @@ class ProductTest { ProductFixtures.storeProduct(18).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).isEqualTo(defaultAccessoryText) assertThat( ProductFixtures.storeProduct(19).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).isEqualTo(defaultAccessoryText) assertThat( ProductFixtures.storeProduct(25).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).isEqualTo(defaultAccessoryText) assertThat( ProductFixtures.storeProduct(26).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).isEqualTo(defaultAccessoryText) } @@ -85,29 +85,29 @@ class ProductTest { ProductFixtures.storeProduct(18).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).contains(hasProductAccessoryTopText) assertThat( ProductFixtures.storeProduct(19).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).contains(hasProductAccessoryTopText) assertThat( ProductFixtures.storeProduct(25).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).contains(hasProductAccessoryTopText) assertThat( ProductFixtures.storeProduct(26).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).contains(hasProductAccessoryTopText) } @@ -118,14 +118,15 @@ class ProductTest { val oneSizeCloseTopText = context.getString(R.string.inpage_one_size_close_top_text) assertThat( ProductFixtures.storeProduct( - sizeList = mutableListOf( - ProductSize("FREE", mutableSetOf()) - ) + sizeList = + mutableListOf( + ProductSize("FREE", mutableSetOf()), + ), ).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).contains(oneSizeCloseTopText) } @@ -136,14 +137,15 @@ class ProductTest { val oneSizeSmallerTopText = context.getString(R.string.inpage_one_size_smaller_top_text) assertThat( ProductFixtures.storeProduct( - sizeList = mutableListOf( - ProductSize("FREE", mutableSetOf()) - ) + sizeList = + mutableListOf( + ProductSize("FREE", mutableSetOf()), + ), ).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).contains(oneSizeSmallerTopText) } @@ -155,14 +157,15 @@ class ProductTest { val oneSizeLargerTopText = context.getString(R.string.inpage_one_size_larger_top_text) assertThat( ProductFixtures.storeProduct( - sizeList = mutableListOf( - ProductSize("FREE", mutableSetOf()) - ) + sizeList = + mutableListOf( + ProductSize("FREE", mutableSetOf()), + ), ).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).contains(oneSizeLargerTopText) } @@ -172,38 +175,42 @@ class ProductTest { val oneSizeBodyProfileText = context.getString(R.string.inpage_one_size_body_profile_text) assertThat( ProductFixtures.storeProduct( - sizeList = mutableListOf( - ProductSize("FREE", mutableSetOf()) - ) + sizeList = + mutableListOf( + ProductSize("FREE", mutableSetOf()), + ), ).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).contains(oneSizeBodyProfileText) } @Test fun getRecText_multiSizeProduct_hasSizeComparisonRecSize_returnMultiSizeComparisonText() { sizeComparisonRecommendedSize = SizeComparisonRecommendedSize() - sizeComparisonRecommendedSize?.bestStoreProductSize = ProductFixtures.storeProduct( - sizeList = mutableListOf( - ProductSize("S", mutableSetOf()), - ProductSize("M", mutableSetOf()) - ) - ).sizes[0] + sizeComparisonRecommendedSize?.bestStoreProductSize = + ProductFixtures.storeProduct( + sizeList = + mutableListOf( + ProductSize("S", mutableSetOf()), + ProductSize("M", mutableSetOf()), + ), + ).sizes[0] val multiSizeComparisonText = context.getString(R.string.inpage_multi_size_comparison_text) assertThat( ProductFixtures.storeProduct( - sizeList = mutableListOf( - ProductSize("S", mutableSetOf()), - ProductSize("M", mutableSetOf()) - ) + sizeList = + mutableListOf( + ProductSize("S", mutableSetOf()), + ProductSize("M", mutableSetOf()), + ), ).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).contains(multiSizeComparisonText) } @@ -214,15 +221,16 @@ class ProductTest { context.getString(R.string.inpage_multi_size_body_profile_text) assertThat( ProductFixtures.storeProduct( - sizeList = mutableListOf( - ProductSize("S", mutableSetOf()), - ProductSize("M", mutableSetOf()) - ) + sizeList = + mutableListOf( + ProductSize("S", mutableSetOf()), + ProductSize("M", mutableSetOf()), + ), ).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).contains(multiSizeBodyProfileText) } @@ -231,15 +239,16 @@ class ProductTest { val noDataText = context.getString(R.string.inpage_no_data_text) assertThat( ProductFixtures.storeProduct( - sizeList = mutableListOf( - ProductSize("S", mutableSetOf()), - ProductSize("M", mutableSetOf()) - ) + sizeList = + mutableListOf( + ProductSize("S", mutableSetOf()), + ProductSize("M", mutableSetOf()), + ), ).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).contains(noDataText) } @@ -250,22 +259,22 @@ class ProductTest { ProductFixtures.storeProduct(4).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).isEqualTo(noDataText) assertThat( ProductFixtures.storeProduct(7).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).isEqualTo(noDataText) assertThat( ProductFixtures.storeProduct(15).getRecommendationText( i18nLocalization, sizeComparisonRecommendedSize, - bodyProfileRecommendedSizeName - ) + bodyProfileRecommendedSizeName, + ), ).isEqualTo(noDataText) } } diff --git a/virtusize-core/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt b/virtusize-core/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt index 559e3bb9..112c8ccb 100644 --- a/virtusize-core/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt +++ b/virtusize-core/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt @@ -12,128 +12,131 @@ import org.json.JSONArray import org.json.JSONObject internal object ProductFixtures { - internal val PRODUCT_TYPE_ID_ONE_JSON_OBJECT_STRING = """ - { - "id": 1, - "name": "dress", - "weights": { - "bust": 1, - "waist": 1, - "height": 0.25 - }, - "compatibleWith": [ - 1, - 16 - ] - } + { + "id": 1, + "name": "dress", + "weights": { + "bust": 1, + "waist": 1, + "height": 0.25 + }, + "compatibleWith": [ + 1, + 16 + ] + } """.trimIndent() private val PRODUCT_TYPE_ID_TWO_JSON_OBJECT_STRING = """ - { - "id": 2, - "name": "shirt", - "weights": { - "bust": 2, - "height": 0.5, - "sleeve": 1 - }, - "compatibleWith":[ - 2 - ] - } + { + "id": 2, + "name": "shirt", + "weights": { + "bust": 2, + "height": 0.5, + "sleeve": 1 + }, + "compatibleWith":[ + 2 + ] + } """.trimIndent() private val PRODUCT_TYPE_ID_EIGHT_JSON_OBJECT_STRING = """ - { - "id": 8, - "name": "jacket", - "weights": { - "bust": 2, - "height": 1, - "sleeve": 1 - }, - "compatibleWith": [ - 8, - 14 - ] - } + { + "id": 8, + "name": "jacket", + "weights": { + "bust": 2, + "height": 1, + "sleeve": 1 + }, + "compatibleWith": [ + 8, + 14 + ] + } """.trimIndent() private val PRODUCT_TYPE_ID_EIGHTEEN_JSON_OBJECT_STRING = """ - { - "id":18, - "name": "bag", - "weights":{ - "depth":1, - "width":2, - "height":1 - }, - "compatibleWith":[ - 18, - 19, - 25, - 26 - ] - } + { + "id":18, + "name": "bag", + "weights":{ + "depth":1, + "width":2, + "height":1 + }, + "compatibleWith":[ + 18, + 19, + 25, + 26 + ] + } """.trimIndent() val PRODUCT_TYPE_JSON_OBJECT = JSONObject(PRODUCT_TYPE_ID_ONE_JSON_OBJECT_STRING) - val PRODUCT_TYPE_JSON_ARRAY = JSONArray( - """ + val PRODUCT_TYPE_JSON_ARRAY = + JSONArray( + """ [ $PRODUCT_TYPE_ID_ONE_JSON_OBJECT_STRING, $PRODUCT_TYPE_ID_TWO_JSON_OBJECT_STRING, $PRODUCT_TYPE_ID_EIGHT_JSON_OBJECT_STRING, $PRODUCT_TYPE_ID_EIGHTEEN_JSON_OBJECT_STRING ] - """.trimIndent() - ) + """.trimIndent(), + ) - fun productTypes() = run { - val productTypes: MutableList = mutableListOf() - for (i in 0 until PRODUCT_TYPE_JSON_ARRAY.length()) { - ProductTypeJsonParser().parse(PRODUCT_TYPE_JSON_ARRAY[i] as JSONObject)?.let { - productTypes.add(it) + fun productTypes() = + run { + val productTypes: MutableList = mutableListOf() + for (i in 0 until PRODUCT_TYPE_JSON_ARRAY.length()) { + ProductTypeJsonParser().parse(PRODUCT_TYPE_JSON_ARRAY[i] as JSONObject)?.let { + productTypes.add(it) + } } + productTypes } - productTypes - } fun storeProduct( productType: Int = 8, - sizeList: List = mutableListOf( - ProductSize( - "38", - mutableSetOf( - Measurement("height", 760), - Measurement("bust", 660), - Measurement("sleeve", 845) - ) + sizeList: List = + mutableListOf( + ProductSize( + "38", + mutableSetOf( + Measurement("height", 760), + Measurement("bust", 660), + Measurement("sleeve", 845), + ), + ), + ProductSize( + "36", + mutableSetOf( + Measurement("height", 750), + Measurement("bust", 645), + Measurement("sleeve", 825), + ), + ), ), - ProductSize( - "36", - mutableSetOf( - Measurement("height", 750), - Measurement("bust", 645), - Measurement("sleeve", 825) - ) - ) - ), brand: String = "Virtusize", - modelInfo: Map? = mutableMapOf( - "hip" to 85, - "size" to "38", - "waist" to 56, - "bust" to 78, - "height" to 165 - ), - gender: String? = "female" + modelInfo: Map? = + mutableMapOf( + "hip" to 85, + "size" to "38", + "waist" to 56, + "bust" to 78, + "height" to 165, + ), + gender: String? = "female", ): Product { return Product( 7110384, @@ -153,42 +156,42 @@ internal object ProductFixtures { modelInfo, "regular", "fashionable", - BrandSizing("large", false) + BrandSizing("large", false), ), brand, - gender - ) + gender, + ), ) } private val USER_PRODUCT_ONE_JSON_STRING = """ - { - "id": 123456, - "sizes": [ - { - "name": "S", - "measurements": { - "height": 1000, - "bust": 400, - "waist": 340, - "hip": null, - "hem": null, - "waistHeight": null - } - } - ], - "productType": 11, - "created": "2020-09-14T11:06:00Z", - "updated": "2020-09-14T11:06:00Z", - "name": "Test Womenswear Strapless Dress", - "cloudinaryPublicId": null, - "deleted": false, - "isFavorite": false, - "wardrobe": 123, - "orderItem": null, - "store": null - } + { + "id": 123456, + "sizes": [ + { + "name": "S", + "measurements": { + "height": 1000, + "bust": 400, + "waist": 340, + "hip": null, + "hem": null, + "waistHeight": null + } + } + ], + "productType": 11, + "created": "2020-09-14T11:06:00Z", + "updated": "2020-09-14T11:06:00Z", + "name": "Test Womenswear Strapless Dress", + "cloudinaryPublicId": null, + "deleted": false, + "isFavorite": false, + "wardrobe": 123, + "orderItem": null, + "store": null + } """.trimIndent() val USER_PRODUCT_ONE_JSON_OBJECT = JSONObject(USER_PRODUCT_ONE_JSON_STRING) 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 4b72f6c8..feb1ab1c 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 @@ -7,7 +7,6 @@ import com.virtusize.android.data.remote.UserBodyProfile import org.json.JSONObject internal object TestFixtures { - const val API_KEY = "test_apiKey" const val USER_ID = "123" const val EXTERNAL_ID = "694" @@ -15,8 +14,9 @@ internal object TestFixtures { val EMPTY_JSON_DATA = JSONObject("{}") - val PRODUCT_DATA_CHECK = JSONObject( - """ + val PRODUCT_DATA_CHECK = + JSONObject( + """ { "data":{ "productTypeName": "pants", @@ -33,15 +33,16 @@ internal object TestFixtures { "name": "backend-checked-product", "productId": "$EXTERNAL_ID" } - """.trimIndent() - ) + """.trimIndent(), + ) val PRODUCT_CHECK = ProductCheckJsonParser().parse(PRODUCT_DATA_CHECK) val VIRTUSIZE_PRODUCT = VirtusizeProduct(EXTERNAL_ID, "http://image.com/xxx.jpg", PRODUCT_CHECK) - val PRODUCT_DATA_CHECK_DATA = JSONObject( - """ + val PRODUCT_DATA_CHECK_DATA = + JSONObject( + """ { "productTypeName": "pants", "storeName": "virtusize", @@ -54,88 +55,91 @@ internal object TestFixtures { "should_see_ph_tooltip": false } } - """.trimIndent() - ) + """.trimIndent(), + ) - val USER_BODY_JSONObject = JSONObject( - """ - { - "wardrobe": "1234567", - "gender": "female", - "age": 32, - "height": 1630, - "weight": "50.00", - "braSize": {}, - "concernAreas": {}, - "bodyData": { - "hip": 830, - "bust": 755, - "neck": 300, - "rise": 215, - "bicep": 220, - "thigh": 480, - "waist": 630, - "inseam": 700, - "sleeve": 720, - "shoulder": 370, - "hipWidth": 300, - "bustWidth": 245, - "hipHeight": 750, - "headHeight": 215, - "kneeHeight": 395, - "waistWidth": 225, - "waistHeight": 920, - "armpitHeight": 1130, - "sleeveLength": 520, - "shoulderWidth": 340, - "shoulderHeight": 1240 - } - } - """.trimIndent() - ) + val USER_BODY_JSONObject = + JSONObject( + """ + { + "wardrobe": "1234567", + "gender": "female", + "age": 32, + "height": 1630, + "weight": "50.00", + "braSize": {}, + "concernAreas": {}, + "bodyData": { + "hip": 830, + "bust": 755, + "neck": 300, + "rise": 215, + "bicep": 220, + "thigh": 480, + "waist": 630, + "inseam": 700, + "sleeve": 720, + "shoulder": 370, + "hipWidth": 300, + "bustWidth": 245, + "hipHeight": 750, + "headHeight": 215, + "kneeHeight": 395, + "waistWidth": 225, + "waistHeight": 920, + "armpitHeight": 1130, + "sleeveLength": 520, + "shoulderWidth": 340, + "shoulderHeight": 1240 + } + } + """.trimIndent(), + ) - val NULL_USER_BODY_PROFILE = JSONObject( - """ - { - "gender": "", - "age": null, - "height": null, - "weight": null, - "braSize": null, - "concernAreas": null, - "bodyData": null - } - """.trimIndent() - ) + val NULL_USER_BODY_PROFILE = + JSONObject( + """ + { + "gender": "", + "age": null, + "height": null, + "weight": null, + "braSize": null, + "concernAreas": null, + "bodyData": null + } + """.trimIndent(), + ) - val userBodyProfile = UserBodyProfile( - "female", - 32, - 1630, - "50.00", - mutableSetOf( - Measurement("hip", 830), - Measurement("hip", 830), - Measurement("bust", 755), - Measurement("neck", 300), - Measurement("rise", 215), - Measurement("bicep", 220), - Measurement("thigh", 480), - Measurement("waist", 630), - Measurement("inseam", 700), - Measurement("sleeve", 720), - Measurement("shoulder", 370), - Measurement("hipWidth", 300), - Measurement("bustWidth", 245), - Measurement("hipHeight", 750), - Measurement("headHeight", 215), - Measurement("kneeHeight", 395), - Measurement("waistWidth", 225), - Measurement("waistHeight", 920), - Measurement("armpitHeight", 1130), - Measurement("sleeveLength", 520), - Measurement("shoulderWidth", 340), - Measurement("shoulderHeight", 1240) + val userBodyProfile = + UserBodyProfile( + "female", + 32, + 1630, + "50.00", + mutableSetOf( + Measurement("hip", 830), + Measurement("hip", 830), + Measurement("bust", 755), + Measurement("neck", 300), + Measurement("rise", 215), + Measurement("bicep", 220), + Measurement("thigh", 480), + Measurement("waist", 630), + Measurement("inseam", 700), + Measurement("sleeve", 720), + Measurement("shoulder", 370), + Measurement("hipWidth", 300), + Measurement("bustWidth", 245), + Measurement("hipHeight", 750), + Measurement("headHeight", 215), + Measurement("kneeHeight", 395), + Measurement("waistWidth", 225), + Measurement("waistHeight", 920), + Measurement("armpitHeight", 1130), + Measurement("sleeveLength", 520), + Measurement("shoulderWidth", 340), + Measurement("shoulderHeight", 1240), + ), ) - ) } 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 7b9625ec..24a1a59a 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 @@ -33,11 +33,12 @@ class VirtusizeApiTaskTest { @Before fun setup() { - virtusizeApiTask = VirtusizeApiTask( - null, - SharedPreferencesHelper.getInstance(context), - null - ) + virtusizeApiTask = + VirtusizeApiTask( + null, + SharedPreferencesHelper.getInstance(context), + null, + ) } @Test @@ -45,15 +46,17 @@ class VirtusizeApiTaskTest { virtusizeApiTask .setJsonParser(UserBodyProfileJsonParser()) - val parseInputStreamStringToObjectMethod = VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseInputStreamStringToObject" } + val parseInputStreamStringToObjectMethod = + VirtusizeApiTask::class.java.declaredMethods + .find { it.name == "parseInputStreamStringToObject" } parseInputStreamStringToObjectMethod?.let { method -> method.isAccessible = true - val returnValue = method.invoke( - virtusizeApiTask, - "", - TestFixtures.USER_BODY_JSONObject.toString() - ) + val returnValue = + method.invoke( + virtusizeApiTask, + "", + TestFixtures.USER_BODY_JSONObject.toString(), + ) val actualUserBodyProfile = returnValue as? UserBodyProfile assertThat(actualUserBodyProfile?.age).isEqualTo(32) assertThat(actualUserBodyProfile?.gender).isEqualTo("female") @@ -82,8 +85,8 @@ class VirtusizeApiTaskTest { Measurement("armpitHeight", 1130), Measurement("sleeveLength", 520), Measurement("shoulderWidth", 340), - Measurement("shoulderHeight", 1240) - ) + Measurement("shoulderHeight", 1240), + ), ) } } @@ -93,16 +96,18 @@ class VirtusizeApiTaskTest { virtusizeApiTask .setJsonParser(UserBodyProfileJsonParser()) - val parseInputStreamStringToObjectMethod = VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseInputStreamStringToObject" } + val parseInputStreamStringToObjectMethod = + VirtusizeApiTask::class.java.declaredMethods + .find { it.name == "parseInputStreamStringToObject" } parseInputStreamStringToObjectMethod?.let { method -> method.isAccessible = true - val returnValue = method.invoke( - virtusizeApiTask, - "", - "{\"gender\":\"\",\"age\":null,\"height\":null,\"weight\":null,\"braSize\":null," + - "\"concernAreas\":null,\"bodyData\":null}" - ) + val returnValue = + method.invoke( + virtusizeApiTask, + "", + "{\"gender\":\"\",\"age\":null,\"height\":null,\"weight\":null,\"braSize\":null," + + "\"concernAreas\":null,\"bodyData\":null}", + ) assertThat(returnValue).isNull() } } @@ -112,43 +117,47 @@ class VirtusizeApiTaskTest { virtusizeApiTask .setJsonParser(ProductCheckJsonParser()) - val parseErrorStreamStringToObjectMethod = VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseErrorStreamStringToObject" } + val parseErrorStreamStringToObjectMethod = + VirtusizeApiTask::class.java.declaredMethods + .find { it.name == "parseErrorStreamStringToObject" } parseErrorStreamStringToObjectMethod?.let { method -> method.isAccessible = true - val pdcJsonString = """ - { - "data": { - "productDataId": null, - "userData": {}, - "storeId": 2, - "storeName": "virtusize", - "validProduct": false, - "fetchMetaData": false - }, - "name": "backend-checked-product", - "productId": "123" - } - """.trimIndent() - val returnValue = method.invoke( - virtusizeApiTask, - pdcJsonString - ) - val expectedProductCheck = ProductCheck( - Data( - validProduct = false, - fetchMetaData = false, - shouldSeePhTooltip = false, - productDataId = 0, - productTypeName = "", - storeName = "virtusize", - storeId = 2, - productTypeId = 0 - ), - productId = "123", - name = "backend-checked-product", - JSONObject(pdcJsonString).toString() - ) + val pdcJsonString = + """ + { + "data": { + "productDataId": null, + "userData": {}, + "storeId": 2, + "storeName": "virtusize", + "validProduct": false, + "fetchMetaData": false + }, + "name": "backend-checked-product", + "productId": "123" + } + """.trimIndent() + val returnValue = + method.invoke( + virtusizeApiTask, + pdcJsonString, + ) + val expectedProductCheck = + ProductCheck( + Data( + validProduct = false, + fetchMetaData = false, + shouldSeePhTooltip = false, + productDataId = 0, + productTypeName = "", + storeName = "virtusize", + storeId = 2, + productTypeId = 0, + ), + productId = "123", + name = "backend-checked-product", + JSONObject(pdcJsonString).toString(), + ) assertThat(returnValue).isEqualTo(expectedProductCheck) } } @@ -158,14 +167,16 @@ class VirtusizeApiTaskTest { virtusizeApiTask .setJsonParser(UserProductJsonParser()) - val parseErrorStreamStringToObjectMethod = VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseErrorStreamStringToObject" } + val parseErrorStreamStringToObjectMethod = + VirtusizeApiTask::class.java.declaredMethods + .find { it.name == "parseErrorStreamStringToObject" } parseErrorStreamStringToObjectMethod?.let { method -> method.isAccessible = true - val returnValue = method.invoke( - virtusizeApiTask, - "{\"detail\":\"No wardrobe found\"}" - ) + val returnValue = + method.invoke( + virtusizeApiTask, + "{\"detail\":\"No wardrobe found\"}", + ) assertThat(returnValue).isEqualTo("{\"detail\":\"No wardrobe found\"}") } } @@ -175,15 +186,17 @@ class VirtusizeApiTaskTest { virtusizeApiTask .setJsonParser(ProductTypeJsonParser()) - val parseStringToObjectMethod = VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseStringToObject" } + val parseStringToObjectMethod = + VirtusizeApiTask::class.java.declaredMethods + .find { it.name == "parseStringToObject" } parseStringToObjectMethod?.let { method -> method.isAccessible = true - val returnValue = method.invoke( - virtusizeApiTask, - "https://staging.virtusize.jp/a/api/v3/product-types", - ProductFixtures.PRODUCT_TYPE_JSON_ARRAY.toString() - ) + val returnValue = + method.invoke( + virtusizeApiTask, + "https://staging.virtusize.jp/a/api/v3/product-types", + ProductFixtures.PRODUCT_TYPE_JSON_ARRAY.toString(), + ) val productTypes = returnValue as List assertThat(productTypes.size).isEqualTo(4) } @@ -194,40 +207,44 @@ class VirtusizeApiTaskTest { virtusizeApiTask .setJsonParser(UserSessionInfoJsonParser()) - val parseStringToObjectMethod = VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseStringToObject" } + val parseStringToObjectMethod = + VirtusizeApiTask::class.java.declaredMethods + .find { it.name == "parseStringToObject" } parseStringToObjectMethod?.let { method -> method.isAccessible = true - val streamString = """ - { - "id":"test_access_token", - "expiresAt":1619062232, - "user":{ - "id":null, - "bid":"test_bid", - "authType":"EMPTY", - "created":null, - "lastLogin":null, - "firstName":"Anonymous", - "language":null - }, - "x-vs-auth":"" - } - """.trimIndent().replace("\\s+|[\\n]+".toRegex(), "") + val streamString = + """ + { + "id":"test_access_token", + "expiresAt":1619062232, + "user":{ + "id":null, + "bid":"test_bid", + "authType":"EMPTY", + "created":null, + "lastLogin":null, + "firstName":"Anonymous", + "language":null + }, + "x-vs-auth":"" + } + """.trimIndent().replace("\\s+|[\\n]+".toRegex(), "") - val returnValue = method.invoke( - virtusizeApiTask, - "https://staging.virtusize.jp/a/api/v3/sessions", - streamString - ) + val returnValue = + method.invoke( + virtusizeApiTask, + "https://staging.virtusize.jp/a/api/v3/sessions", + streamString, + ) - val expectedUserSessionInfo = UserSessionInfo( - accessToken = "test_access_token", - bid = "test_bid", - authToken = "", - userSessionResponse = streamString - ) + val expectedUserSessionInfo = + UserSessionInfo( + accessToken = "test_access_token", + bid = "test_bid", + authToken = "", + userSessionResponse = streamString, + ) assertThat(returnValue).isEqualTo(expectedUserSessionInfo) } } @@ -237,15 +254,17 @@ class VirtusizeApiTaskTest { virtusizeApiTask .setJsonParser(UserBodyProfileJsonParser()) - val parseStringToObjectMethod = VirtusizeApiTask::class.java.declaredMethods - .find { it.name == "parseStringToObject" } + val parseStringToObjectMethod = + VirtusizeApiTask::class.java.declaredMethods + .find { it.name == "parseStringToObject" } parseStringToObjectMethod?.let { method -> method.isAccessible = true - val returnValue = method.invoke( - virtusizeApiTask, - "https://staging.virtusize.jp/a/api/v3/user-body-measurements", - """ + val returnValue = + method.invoke( + virtusizeApiTask, + "https://staging.virtusize.jp/a/api/v3/user-body-measurements", + """ { "gender":"", "age":null, @@ -255,8 +274,8 @@ class VirtusizeApiTaskTest { "concernAreas":null, "bodyData":null } - """.trimIndent() - ) + """.trimIndent(), + ) assertThat(returnValue).isNull() } 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 614be219..bca2c7f1 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 @@ -25,19 +25,18 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.Q]) class VirtusizeApiTest { - private val context: Context = ApplicationProvider.getApplicationContext() private val defaultDisplay = (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay private val resolution = "${defaultDisplay.height}x${defaultDisplay.width}" - private val versionCode = BuildConfig.VERSION_NAME + private val versionCode = BuildConfig.VERSION_NANE @Before fun initVirtusizeApi() { VirtusizeApi.init( env = VirtusizeEnvironment.STAGING, key = TestFixtures.API_KEY, - userId = TestFixtures.USER_ID + userId = TestFixtures.USER_ID, ) } @@ -45,10 +44,11 @@ class VirtusizeApiTest { fun productCheck_shouldReturnExpectedApiRequest() { val actualApiRequest = VirtusizeApi.productCheck(TestFixtures.VIRTUSIZE_PRODUCT) - val expectedUrl = "https://services.virtusize.com/stg/product/check" + - "?apiKey=${TestFixtures.API_KEY}" + - "&externalId=${TestFixtures.EXTERNAL_ID}" + - "&version=1" + val expectedUrl = + "https://services.virtusize.com/stg/product/check" + + "?apiKey=${TestFixtures.API_KEY}" + + "&externalId=${TestFixtures.EXTERNAL_ID}" + + "&version=1" val expectedApiRequest = ApiRequest(expectedUrl, HttpMethod.GET) @@ -69,7 +69,7 @@ class VirtusizeApiTest { VirtusizeApi.init( VirtusizeEnvironment.JAPAN, TestFixtures.API_KEY, - TestFixtures.USER_ID + TestFixtures.USER_ID, ) val actualUrl = VirtusizeApi.virtusizeWebViewURL() val expectedUrl = "https://static.api.virtusize.jp/a/aoyama/latest/sdk-webview.html" @@ -101,26 +101,28 @@ class VirtusizeApiTest { fun sendUserSawProductEventToAPI_shouldReturnExpectedApiRequest() { val eventName = VirtusizeEvents.UserSawProduct.getEventName() val event = VirtusizeEvent(VirtusizeEvents.UserSawProduct.getEventName()) - val actualApiRequest = VirtusizeApi.sendEventToAPI( - event, - TestFixtures.PRODUCT_CHECK, - TestFixtures.ORIENTATION, - resolution, - versionCode - ) + val actualApiRequest = + VirtusizeApi.sendEventToAPI( + event, + TestFixtures.PRODUCT_CHECK, + TestFixtures.ORIENTATION, + resolution, + versionCode, + ) - val expectedParams = mutableMapOf( - "name" to eventName, - "apiKey" to TestFixtures.API_KEY, - "type" to "user", - "source" to "integration-android", - "userCohort" to "direct", - "widgetType" to "mobile", - "browserOrientation" to TestFixtures.ORIENTATION, - "browserResolution" to resolution, - "integrationVersion" to versionCode.toString(), - "snippetVersion" to versionCode.toString() - ) + val expectedParams = + mutableMapOf( + "name" to eventName, + "apiKey" to TestFixtures.API_KEY, + "type" to "user", + "source" to "integration-android", + "userCohort" to "direct", + "widgetType" to "mobile", + "browserOrientation" to TestFixtures.ORIENTATION, + "browserResolution" to resolution, + "integrationVersion" to versionCode.toString(), + "snippetVersion" to versionCode.toString(), + ) TestFixtures.PRODUCT_CHECK?.let { productCheck -> expectedParams["storeProductExternalId"] = productCheck.productId @@ -144,43 +146,46 @@ class VirtusizeApiTest { @Test fun sendOrder_shouldReturnExpectedApiRequest() { val order = VirtusizeOrder("888400111032") - order.items = mutableListOf( - VirtusizeOrderItem( - "P001", - "L", - "Large", - "P001_SIZEL_RED", - "http://images.example.com/products/P001/red/image1xl.jpg", - "Red", - "W", - 5100.00, - "JPY", - 1, - "http://example.com/products/P001" + order.items = + mutableListOf( + VirtusizeOrderItem( + "P001", + "L", + "Large", + "P001_SIZEL_RED", + "http://images.example.com/products/P001/red/image1xl.jpg", + "Red", + "W", + 5100.00, + "JPY", + 1, + "http://example.com/products/P001", + ), ) - ) val actualApiRequest = VirtusizeApi.sendOrder(order) - val expectedParams = mutableMapOf( - "apiKey" to TestFixtures.API_KEY, - "externalOrderId" to "888400111032", - "externalUserId" to TestFixtures.USER_ID, - "items" to mutableListOf>( - mutableMapOf( - "externalProductId" to "P001", - "size" to "L", - "sizeAlias" to "Large", - "variantId" to "P001_SIZEL_RED", - "imageUrl" to "http://images.example.com/products/P001/red/image1xl.jpg", - "color" to "Red", - "gender" to "W", - "unitPrice" to 5100.00, - "currency" to "JPY", - "quantity" to 1, - "url" to "http://example.com/products/P001" - ) + val expectedParams = + mutableMapOf( + "apiKey" to TestFixtures.API_KEY, + "externalOrderId" to "888400111032", + "externalUserId" to TestFixtures.USER_ID, + "items" to + mutableListOf>( + mutableMapOf( + "externalProductId" to "P001", + "size" to "L", + "sizeAlias" to "Large", + "variantId" to "P001_SIZEL_RED", + "imageUrl" to "http://images.example.com/products/P001/red/image1xl.jpg", + "color" to "Red", + "gender" to "W", + "unitPrice" to 5100.00, + "currency" to "JPY", + "quantity" to 1, + "url" to "http://example.com/products/P001", + ), + ), ) - ) assertThat(actualApiRequest.url).isEqualTo("https://staging.virtusize.com/a/api/v3/orders") assertThat(actualApiRequest.method).isEquivalentAccordingToCompareTo(HttpMethod.POST) @@ -191,8 +196,9 @@ class VirtusizeApiTest { fun retrieveStoreInfo_shouldReturnExpectedApiRequest() { val actualApiRequest = VirtusizeApi.getStoreInfo() - val expectedUrl = "https://staging.virtusize.com/a/api/v3/stores/api-key/test_apiKey" + - "?format=json" + val expectedUrl = + "https://staging.virtusize.com/a/api/v3/stores/api-key/test_apiKey" + + "?format=json" val expectedApiRequest = ApiRequest(expectedUrl, HttpMethod.GET) @@ -203,8 +209,9 @@ class VirtusizeApiTest { fun getStoreProductInfo_shouldReturnExpectedApiRequest() { val actualApiRequest = VirtusizeApi.getStoreProductInfo("16099122") - val expectedUrl = "https://staging.virtusize.com/a/api/v3/store-products/16099122" + - "?format=json" + val expectedUrl = + "https://staging.virtusize.com/a/api/v3/store-products/16099122" + + "?format=json" val expectedApiRequest = ApiRequest(expectedUrl, HttpMethod.GET) @@ -268,11 +275,12 @@ class VirtusizeApiTest { @Test fun getSize_shouldReturnExpectedApiRequest() { - val actualApiRequest = VirtusizeApi.getSize( - ProductFixtures.productTypes(), - ProductFixtures.storeProduct(), - TestFixtures.userBodyProfile - ) + val actualApiRequest = + VirtusizeApi.getSize( + ProductFixtures.productTypes(), + ProductFixtures.storeProduct(), + TestFixtures.userBodyProfile, + ) val expectedUrl = "https://size-recommendation.staging.virtusize.jp/item" @@ -284,95 +292,117 @@ class VirtusizeApiTest { assertThat(actualApiRequest.params["userWeight"]).isEqualTo(50) assertThat(actualApiRequest.params["bodyData"]).isEqualTo( mutableMapOf( - "waistWidth" to mutableMapOf( - "value" to 225, - "predicted" to true - ), - "chest" to mutableMapOf( - "value" to 755, - "predicted" to true - ), - "bustWidth" to mutableMapOf( - "value" to 245, - "predicted" to true - ), - "thigh" to mutableMapOf( - "value" to 480, - "predicted" to true - ), - "shoulderWidth" to mutableMapOf( - "value" to 340, - "predicted" to true - ), - "hipHeight" to mutableMapOf( - "value" to 750, - "predicted" to true - ), - "kneeHeight" to mutableMapOf( - "value" to 395, - "predicted" to true - ), - "neck" to mutableMapOf( - "value" to 300, - "predicted" to true - ), - "waistHeight" to mutableMapOf( - "value" to 920, - "predicted" to true - ), - "hip" to mutableMapOf( - "value" to 830, - "predicted" to true - ), - "armpitHeight" to mutableMapOf( - "value" to 1130, - "predicted" to true - ), - "bicep" to mutableMapOf( - "value" to 220, - "predicted" to true - ), - "inseam" to mutableMapOf( - "value" to 700, - "predicted" to true - ), - "headHeight" to mutableMapOf( - "value" to 215, - "predicted" to true - ), - "hipWidth" to mutableMapOf( - "value" to 300, - "predicted" to true - ), - "sleeve" to mutableMapOf( - "value" to 720, - "predicted" to true - ), - "bust" to mutableMapOf( - "value" to 755, - "predicted" to true - ), - "waist" to mutableMapOf( - "value" to 630, - "predicted" to true - ), - "sleeveLength" to mutableMapOf( - "value" to 520, - "predicted" to true - ), - "rise" to mutableMapOf( - "value" to 215, - "predicted" to true - ), - "shoulder" to mutableMapOf( - "value" to 370, - "predicted" to true - ), - "shoulderHeight" to mutableMapOf( - "value" to 1240, - "predicted" to true - ) - ) + "waistWidth" to + mutableMapOf( + "value" to 225, + "predicted" to true, + ), + "chest" to + mutableMapOf( + "value" to 755, + "predicted" to true, + ), + "bustWidth" to + mutableMapOf( + "value" to 245, + "predicted" to true, + ), + "thigh" to + mutableMapOf( + "value" to 480, + "predicted" to true, + ), + "shoulderWidth" to + mutableMapOf( + "value" to 340, + "predicted" to true, + ), + "hipHeight" to + mutableMapOf( + "value" to 750, + "predicted" to true, + ), + "kneeHeight" to + mutableMapOf( + "value" to 395, + "predicted" to true, + ), + "neck" to + mutableMapOf( + "value" to 300, + "predicted" to true, + ), + "waistHeight" to + mutableMapOf( + "value" to 920, + "predicted" to true, + ), + "hip" to + mutableMapOf( + "value" to 830, + "predicted" to true, + ), + "armpitHeight" to + mutableMapOf( + "value" to 1130, + "predicted" to true, + ), + "bicep" to + mutableMapOf( + "value" to 220, + "predicted" to true, + ), + "inseam" to + mutableMapOf( + "value" to 700, + "predicted" to true, + ), + "headHeight" to + mutableMapOf( + "value" to 215, + "predicted" to true, + ), + "hipWidth" to + mutableMapOf( + "value" to 300, + "predicted" to true, + ), + "sleeve" to + mutableMapOf( + "value" to 720, + "predicted" to true, + ), + "bust" to + mutableMapOf( + "value" to 755, + "predicted" to true, + ), + "waist" to + mutableMapOf( + "value" to 630, + "predicted" to true, + ), + "sleeveLength" to + mutableMapOf( + "value" to 520, + "predicted" to true, + ), + "rise" to + mutableMapOf( + "value" to 215, + "predicted" to true, + ), + "shoulder" to + mutableMapOf( + "value" to 370, + "predicted" to true, + ), + "shoulderHeight" to + mutableMapOf( + "value" to 1240, + "predicted" to true, + ), + ), ) val items = actualApiRequest.params["items"] as Array> for (item in items) { @@ -380,43 +410,49 @@ class VirtusizeApiTest { assertThat(item["productType"]).isEqualTo("jacket") assertThat(item["itemSizesOrig"]).isEqualTo( mutableMapOf( - "38" to mutableMapOf( - "bust" to 660, - "sleeve" to 845, - "height" to 760 - ), - "36" to mutableMapOf( - "bust" to 645, - "sleeve" to 825, - "height" to 750 - ) - ) - ) - assertThat(item["additionalInfo"]).isEqualTo( - mutableMapOf( - "fit" to "regular", - "sizes" to mutableMapOf( - "38" to mutableMapOf( + "38" to + mutableMapOf( "bust" to 660, "sleeve" to 845, - "height" to 760 + "height" to 760, ), - "36" to mutableMapOf( + "36" to + mutableMapOf( "bust" to 645, "sleeve" to 825, - "height" to 750 - ) - ), + "height" to 750, + ), + ), + ) + assertThat(item["additionalInfo"]).isEqualTo( + mutableMapOf( + "fit" to "regular", + "sizes" to + mutableMapOf( + "38" to + mutableMapOf( + "bust" to 660, + "sleeve" to 845, + "height" to 760, + ), + "36" to + mutableMapOf( + "bust" to 645, + "sleeve" to 825, + "height" to 750, + ), + ), "gender" to "female", "brand" to "Virtusize", - "modelInfo" to mutableMapOf( - "waist" to 56, - "bust" to 78, - "size" to "38", - "hip" to 85, - "height" to 165 - ) - ) + "modelInfo" to + mutableMapOf( + "waist" to 56, + "bust" to 78, + "size" to "38", + "hip" to 85, + "height" to 165, + ), + ), ) } } diff --git a/virtusize/.gitignore b/virtusize/.gitignore index 796b96d1..42afabfd 100644 --- a/virtusize/.gitignore +++ b/virtusize/.gitignore @@ -1 +1 @@ -/build +/build \ No newline at end of file diff --git a/virtusize/proguard-rules.pro b/virtusize/proguard-rules.pro index f1b42451..481bb434 100644 --- a/virtusize/proguard-rules.pro +++ b/virtusize/proguard-rules.pro @@ -18,4 +18,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/virtusize/src/main/java/com/virtusize/android/CallbackHandler.kt b/virtusize/src/main/java/com/virtusize/android/CallbackHandler.kt index b71399d4..d6e67cb2 100644 --- a/virtusize/src/main/java/com/virtusize/android/CallbackHandler.kt +++ b/virtusize/src/main/java/com/virtusize/android/CallbackHandler.kt @@ -1,7 +1,5 @@ package com.virtusize.android -import androidx.annotation.NonNull -import androidx.annotation.Nullable import com.virtusize.android.data.local.VirtusizeError /** @@ -12,7 +10,7 @@ interface SuccessResponseHandler { * Passes optional data to any object * @param data Any invoked data that wants to be passed */ - fun onSuccess(@Nullable data: Any? = null) + fun onSuccess(data: Any? = null) } /** @@ -23,5 +21,5 @@ interface ErrorResponseHandler { * Passes the error of type [VirtusizeError] * @param error Any error that wants to be passed */ - fun onError(@NonNull error: VirtusizeError) + fun onError(error: VirtusizeError) } diff --git a/virtusize/src/main/java/com/virtusize/android/Virtusize.kt b/virtusize/src/main/java/com/virtusize/android/Virtusize.kt index 69c007ef..aa782d90 100644 --- a/virtusize/src/main/java/com/virtusize/android/Virtusize.kt +++ b/virtusize/src/main/java/com/virtusize/android/Virtusize.kt @@ -34,9 +34,8 @@ import kotlinx.coroutines.launch */ class Virtusize( private val context: Context, - internal val params: VirtusizeParams + internal val params: VirtusizeParams, ) { - // The getter of the display language val displayLanguage: VirtusizeLanguage get() = params.language @@ -45,179 +44,194 @@ class Virtusize( private val messageHandlers = mutableListOf() // The Virtusize message handler passes received errors and events to registered message handlers - private val messageHandler = object : VirtusizeMessageHandler { - override fun onEvent(product: VirtusizeProduct, event: VirtusizeEvent) { - messageHandlers.forEach { messageHandler -> - messageHandler.onEvent(product, event) - } - // Handle different user events from the web view - when (event.name) { - VirtusizeEvents.UserOpenedWidget.getEventName() -> { - virtusizeRepository.setLastProductOnVirtusizeWebView(product.externalId) - CoroutineScope(Main).launch { - virtusizeRepository.fetchDataForInPageRecommendation( - shouldUpdateUserProducts = false, - shouldUpdateBodyProfile = false - ) - virtusizeRepository.updateInPageRecommendation() - } - } - VirtusizeEvents.UserAuthData.getEventName() -> { - event.data?.let { data -> - virtusizeRepository.updateUserAuthData(data) - } + private val messageHandler = + object : VirtusizeMessageHandler { + override fun onEvent( + product: VirtusizeProduct, + event: VirtusizeEvent, + ) { + messageHandlers.forEach { messageHandler -> + messageHandler.onEvent(product, event) } - VirtusizeEvents.UserSelectedProduct.getEventName() -> { - val userProductId = event.data?.optInt("userProductId") - CoroutineScope(Main).launch { - virtusizeRepository.fetchDataForInPageRecommendation( - selectedUserProductId = userProductId, - shouldUpdateUserProducts = false, - shouldUpdateBodyProfile = false - ) - virtusizeRepository.updateInPageRecommendation( - type = SizeRecommendationType.compareProduct - ) + // Handle different user events from the web view + when (event.name) { + VirtusizeEvents.UserOpenedWidget.getEventName() -> { + virtusizeRepository.setLastProductOnVirtusizeWebView(product.externalId) + CoroutineScope(Main).launch { + virtusizeRepository.fetchDataForInPageRecommendation( + shouldUpdateUserProducts = false, + shouldUpdateBodyProfile = false, + ) + virtusizeRepository.updateInPageRecommendation() + } } - } - VirtusizeEvents.UserAddedProduct.getEventName() -> { - CoroutineScope(Main).launch { - virtusizeRepository.fetchDataForInPageRecommendation( - shouldUpdateUserProducts = true, - shouldUpdateBodyProfile = false - ) - virtusizeRepository.updateInPageRecommendation( - type = SizeRecommendationType.compareProduct - ) + VirtusizeEvents.UserAuthData.getEventName() -> { + event.data?.let { data -> + virtusizeRepository.updateUserAuthData(data) + } } - } - VirtusizeEvents.UserDeletedProduct.getEventName() -> { - event.data?.optInt("userProductId")?.let { userProductId -> - virtusizeRepository.deleteUserProduct(userProductId) + VirtusizeEvents.UserSelectedProduct.getEventName() -> { + val userProductId = event.data?.optInt("userProductId") + CoroutineScope(Main).launch { + virtusizeRepository.fetchDataForInPageRecommendation( + selectedUserProductId = userProductId, + shouldUpdateUserProducts = false, + shouldUpdateBodyProfile = false, + ) + virtusizeRepository.updateInPageRecommendation( + type = SizeRecommendationType.CompareProduct, + ) + } } - CoroutineScope(Main).launch { - virtusizeRepository.fetchDataForInPageRecommendation( - shouldUpdateUserProducts = false, - shouldUpdateBodyProfile = false - ) - virtusizeRepository.updateInPageRecommendation() + VirtusizeEvents.UserAddedProduct.getEventName() -> { + CoroutineScope(Main).launch { + virtusizeRepository.fetchDataForInPageRecommendation( + shouldUpdateUserProducts = true, + shouldUpdateBodyProfile = false, + ) + virtusizeRepository.updateInPageRecommendation( + type = SizeRecommendationType.CompareProduct, + ) + } } - } - VirtusizeEvents.UserChangedRecommendationType.getEventName() -> { - // Switches the view for InPage based on user selected size recommendation type - var recommendationType: SizeRecommendationType? = null - event.data?.optString("recommendationType")?.let { - recommendationType = valueOf(it) + VirtusizeEvents.UserDeletedProduct.getEventName() -> { + event.data?.optInt("userProductId")?.let { userProductId -> + virtusizeRepository.deleteUserProduct(userProductId) + } + CoroutineScope(Main).launch { + virtusizeRepository.fetchDataForInPageRecommendation( + shouldUpdateUserProducts = false, + shouldUpdateBodyProfile = false, + ) + virtusizeRepository.updateInPageRecommendation() + } } - CoroutineScope(Main).launch { - virtusizeRepository.updateInPageRecommendation(type = recommendationType) + VirtusizeEvents.UserChangedRecommendationType.getEventName() -> { + // Switches the view for InPage based on user selected size recommendation type + var recommendationType: SizeRecommendationType? = null + event.data?.optString("recommendationType")?.let { + recommendationType = valueOf(it) + } + CoroutineScope(Main).launch { + virtusizeRepository.updateInPageRecommendation( + type = recommendationType, + ) + } } - } - VirtusizeEvents.UserUpdatedBodyMeasurements.getEventName() -> { - // Updates the body recommendation size and switches the view to the body comparison - val sizeRecName = event.data?.optString("sizeRecName") - CoroutineScope(Main).launch { - virtusizeRepository.updateUserBodyRecommendedSize(sizeRecName) - virtusizeRepository.updateInPageRecommendation( - type = SizeRecommendationType.body - ) + VirtusizeEvents.UserUpdatedBodyMeasurements.getEventName() -> { + // Updates the body recommendation size and switches the view to the body comparison + val sizeRecName = event.data?.optString("sizeRecName") + CoroutineScope(Main).launch { + virtusizeRepository.updateUserBodyRecommendedSize(sizeRecName) + virtusizeRepository.updateInPageRecommendation( + type = SizeRecommendationType.Body, + ) + } } - } - VirtusizeEvents.UserLoggedIn.getEventName() -> { - // Updates the user session and fetches updated user products and body profile from the server - CoroutineScope(Main).launch { - virtusizeRepository.updateUserSession() - virtusizeRepository.fetchDataForInPageRecommendation() - virtusizeRepository.updateInPageRecommendation() + VirtusizeEvents.UserLoggedIn.getEventName() -> { + // Updates the user session and fetches updated user products and body profile from the server + CoroutineScope(Main).launch { + virtusizeRepository.updateUserSession() + virtusizeRepository.fetchDataForInPageRecommendation() + virtusizeRepository.updateInPageRecommendation() + } } - } - VirtusizeEvents.UserLoggedOut.getEventName(), - VirtusizeEvents.UserDeletedData.getEventName() -> { - // Clears user related data and updates the session, - // and then re-fetches user products and body profile from the server - CoroutineScope(Main).launch { - virtusizeRepository.clearUserData() - virtusizeRepository.updateUserSession() - virtusizeRepository.fetchDataForInPageRecommendation( - shouldUpdateUserProducts = false, - shouldUpdateBodyProfile = false - ) - virtusizeRepository.updateInPageRecommendation() + VirtusizeEvents.UserLoggedOut.getEventName(), + VirtusizeEvents.UserDeletedData.getEventName(), + -> { + // Clears user related data and updates the session, + // and then re-fetches user products and body profile from the server + CoroutineScope(Main).launch { + virtusizeRepository.clearUserData() + virtusizeRepository.updateUserSession() + virtusizeRepository.fetchDataForInPageRecommendation( + shouldUpdateUserProducts = false, + shouldUpdateBodyProfile = false, + ) + virtusizeRepository.updateInPageRecommendation() + } } } } - } - override fun onError(error: VirtusizeError) { - messageHandlers.forEach { messageHandler -> - messageHandler.onError(error) + override fun onError(error: VirtusizeError) { + messageHandlers.forEach { messageHandler -> + messageHandler.onError(error) + } } } - } /** * The VirtusizePresenter handles the data passed from the actions of VirtusizeRepository */ - private val virtusizePresenter = object : VirtusizePresenter { - override fun onValidProductDataCheck(productWithPDC: VirtusizeProduct) { - for (virtusizeView in virtusizeViews) { - virtusizeView.setProductWithProductDataCheck(productWithPDC) - } - if (virtusizeViewsContainInPage()) { - CoroutineScope(Main).launch { - virtusizeRepository.fetchInitialData(params.language, productWithPDC) - virtusizeRepository.updateUserSession(productWithPDC.externalId) - virtusizeRepository.fetchDataForInPageRecommendation(productWithPDC.externalId) - virtusizeRepository.updateInPageRecommendation(productWithPDC.externalId) + private val virtusizePresenter = + object : VirtusizePresenter { + override fun onValidProductDataCheck(productWithPDC: VirtusizeProduct) { + for (virtusizeView in virtusizeViews) { + virtusizeView.setProductWithProductDataCheck(productWithPDC) + } + if (virtusizeViewsContainInPage()) { + CoroutineScope(Main).launch { + virtusizeRepository.fetchInitialData(params.language, productWithPDC) + virtusizeRepository.updateUserSession(productWithPDC.externalId) + virtusizeRepository.fetchDataForInPageRecommendation( + productWithPDC.externalId, + ) + virtusizeRepository.updateInPageRecommendation(productWithPDC.externalId) + } } } - } - override fun hasInPageError(externalProductId: String?, error: VirtusizeError?) { - error?.let { messageHandler.onError(it) } - for (virtusizeView in virtusizeViews) { - if (virtusizeView is VirtusizeInPageView) { - virtusizeView.showInPageError(externalProductId) + override fun hasInPageError( + externalProductId: String?, + error: VirtusizeError?, + ) { + error?.let { messageHandler.onError(it) } + for (virtusizeView in virtusizeViews) { + if (virtusizeView is VirtusizeInPageView) { + virtusizeView.showInPageError(externalProductId) + } } } - } - override fun gotSizeRecommendations( - externalProductId: String, - userProductRecommendedSize: SizeComparisonRecommendedSize?, - userBodyRecommendedSize: String? - ) { - val storeProduct = virtusizeRepository.getProductBy(externalProductId) - for (virtusizeView in virtusizeViews) { - if (virtusizeView is VirtusizeInPageView) { - storeProduct?.apply { - virtusizeRepository.i18nLocalization?.let { i18nLocalization -> - val trimType = - if (virtusizeView is VirtusizeInPageStandard) - I18nLocalization.TrimType.MULTIPLELINES - else - I18nLocalization.TrimType.ONELINE - val recommendationText = getRecommendationText( - i18nLocalization, - userProductRecommendedSize, - userBodyRecommendedSize - ).trimI18nText(trimType) - virtusizeView.setRecommendationText( - externalProductId, - recommendationText - ) - } - if (virtusizeView is VirtusizeInPageStandard) { - virtusizeView.setProductImages( - this, - userProductRecommendedSize?.bestUserProduct - ) + override fun gotSizeRecommendations( + externalProductId: String, + userProductRecommendedSize: SizeComparisonRecommendedSize?, + userBodyRecommendedSize: String?, + ) { + val storeProduct = virtusizeRepository.getProductBy(externalProductId) + for (virtusizeView in virtusizeViews) { + if (virtusizeView is VirtusizeInPageView) { + storeProduct?.apply { + virtusizeRepository.i18nLocalization?.let { i18nLocalization -> + val trimType = + if (virtusizeView is VirtusizeInPageStandard) { + I18nLocalization.TrimType.MULTIPLELINES + } else { + I18nLocalization.TrimType.ONELINE + } + val recommendationText = + getRecommendationText( + i18nLocalization, + userProductRecommendedSize, + userBodyRecommendedSize, + ).trimI18nText(trimType) + virtusizeView.setRecommendationText( + externalProductId, + recommendationText, + ) + } + if (virtusizeView is VirtusizeInPageStandard) { + virtusizeView.setProductImages( + this, + userProductRecommendedSize?.bestUserProduct, + ) + } } } } } } - } private var virtusizeRepository: VirtusizeRepository = VirtusizeRepository(context, messageHandler, virtusizePresenter) @@ -231,7 +245,7 @@ class Virtusize( VirtusizeApi.init( env = params.environment, key = params.apiKey!!, - userId = params.externalUserId ?: "" + userId = params.externalUserId ?: "", ) } @@ -285,7 +299,10 @@ class Virtusize( * @param product the [VirtusizeProduct] set by a client * @throws IllegalArgumentException throws an error if VirtusizeButton is null or the image URL of VirtusizeProduct is invalid */ - fun setupVirtusizeView(virtusizeView: VirtusizeView?, product: VirtusizeProduct) { + fun setupVirtusizeView( + virtusizeView: VirtusizeView?, + product: VirtusizeProduct, + ) { // Throws VirtusizeError.NullVirtusizeButtonError error if button is null if (virtusizeView == null) { VirtusizeErrorType.NullVirtusizeViewError.throwError() @@ -295,7 +312,7 @@ class Virtusize( virtusizeView.initialSetup( product = product, params = params, - messageHandler = messageHandler + messageHandler = messageHandler, ) virtusizeViews.add(virtusizeView) @@ -310,7 +327,7 @@ class Virtusize( fun sendOrder( order: VirtusizeOrder, onSuccess: (() -> Unit)? = null, - onError: ((VirtusizeError) -> Unit)? = null + onError: ((VirtusizeError) -> Unit)? = null, ) { CoroutineScope(Main).launch { virtusizeRepository.sendOrder(params, order, { _ -> @@ -330,7 +347,7 @@ class Virtusize( fun sendOrder( order: VirtusizeOrder, onSuccess: com.virtusize.android.SuccessResponseHandler? = null, - onError: com.virtusize.android.ErrorResponseHandler? = null + onError: com.virtusize.android.ErrorResponseHandler? = null, ) { CoroutineScope(Main).launch { virtusizeRepository.sendOrder(params, order, { data -> diff --git a/virtusize/src/main/java/com/virtusize/android/VirtusizeBuilder.kt b/virtusize/src/main/java/com/virtusize/android/VirtusizeBuilder.kt index aaf2ff48..9caf9f69 100644 --- a/virtusize/src/main/java/com/virtusize/android/VirtusizeBuilder.kt +++ b/virtusize/src/main/java/com/virtusize/android/VirtusizeBuilder.kt @@ -118,9 +118,7 @@ class VirtusizeBuilder { * @param detailsPanelCards the list of [VirtusizeInfoCategory] * @return VirtusizeBuilder */ - fun setDetailsPanelCards( - detailsPanelCards: MutableList - ): VirtusizeBuilder { + fun setDetailsPanelCards(detailsPanelCards: MutableList): VirtusizeBuilder { this.detailsPanelCards = detailsPanelCards return this } @@ -137,17 +135,18 @@ class VirtusizeBuilder { if (context == null) { VirtusizeErrorType.NullContext.throwError() } - val params = VirtusizeParams( - context = context!!, - apiKey = apiKey, - environment = env, - region = region, - language = language ?: region.defaultLanguage(), - allowedLanguages = allowedLanguages, - externalUserId = userId, - showSGI = showSGI, - detailsPanelCards = detailsPanelCards - ) + val params = + VirtusizeParams( + context = context!!, + apiKey = apiKey, + environment = env, + region = region, + language = language ?: region.defaultLanguage(), + allowedLanguages = allowedLanguages, + externalUserId = userId, + showSGI = showSGI, + detailsPanelCards = detailsPanelCards, + ) return Virtusize(context = context!!, params = params) } } diff --git a/virtusize/src/main/java/com/virtusize/android/VirtusizeFitIllustratorWebView.kt b/virtusize/src/main/java/com/virtusize/android/VirtusizeFitIllustratorWebView.kt index efb426bf..d0d19218 100644 --- a/virtusize/src/main/java/com/virtusize/android/VirtusizeFitIllustratorWebView.kt +++ b/virtusize/src/main/java/com/virtusize/android/VirtusizeFitIllustratorWebView.kt @@ -35,372 +35,444 @@ import com.virtusize.android.util.isFitIllustratorURL import com.virtusize.android.util.urlString @SuppressLint("SetJavaScriptEnabled") -class VirtusizeFitIllustratorWebView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : WebView(context, attrs, defStyleAttr) { +class VirtusizeFitIllustratorWebView + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : WebView(context, attrs, defStyleAttr) { + private var internalWebChromeClient: WebChromeClient? = null + private var internalWebViewClient: WebViewClient? = null + + override fun setWebChromeClient(client: WebChromeClient?) { + internalWebChromeClient = client + } + + override fun setWebViewClient(client: WebViewClient) { + internalWebViewClient = client + } + + internal val isMultipleWindowsSupported: Boolean + get() = settings.supportMultipleWindows() + + internal fun enableWindowsSettings() { + settings.setSupportMultipleWindows(true) + settings.javaScriptCanOpenWindowsAutomatically = true + } + + init { + isFocusable = true + isFocusableInTouchMode = true + + settings.javaScriptEnabled = true + settings.domStorageEnabled = true + settings.databaseEnabled = true + + super.setWebViewClient( + object : WebViewClient() { + override fun shouldOverrideUrlLoading( + view: WebView?, + url: String?, + ): Boolean { + url?.let { + if (!isMultipleWindowsSupported && url.isFitIllustratorURL) { + VirtusizeFitIllustratorFragment.launch(context, url) + return true + } + } + return internalWebViewClient?.shouldOverrideUrlLoading(view, url) ?: false + } - private var _webChromeClient: WebChromeClient? = null - private var _webViewClient: WebViewClient? = null + @RequiresApi(Build.VERSION_CODES.N) + override fun shouldOverrideUrlLoading( + view: WebView?, + request: WebResourceRequest?, + ): Boolean { + request?.let { + if (!isMultipleWindowsSupported && request.isFitIllustratorURL) { + VirtusizeFitIllustratorFragment.launch(context, request.urlString) + return true + } + } + return internalWebViewClient?.shouldOverrideUrlLoading(view, request) ?: false + } - override fun setWebChromeClient(client: WebChromeClient?) { - _webChromeClient = client - } + override fun onPageStarted( + view: WebView?, + url: String?, + favicon: Bitmap?, + ) { + internalWebViewClient?.onPageStarted(view, url, favicon) + } - override fun setWebViewClient(client: WebViewClient) { - _webViewClient = client - } + override fun onPageFinished( + view: WebView?, + url: String?, + ) { + // This is to close any subviews when a user is successfully logged into Facebook + if ( + isMultipleWindowsSupported && + url?.contains("virtusize") == true && + url.contains("#compare") + ) { + view?.removeAllViews() + } + internalWebViewClient?.onPageFinished(view, url) + } - internal val isMultipleWindowsSupported: Boolean - get() = settings.supportMultipleWindows() + override fun onLoadResource( + view: WebView?, + url: String?, + ) { + internalWebViewClient?.onLoadResource(view, url) + } - internal fun enableWindowsSettings() { - settings.setSupportMultipleWindows(true) - settings.javaScriptCanOpenWindowsAutomatically = true - } + @RequiresApi(Build.VERSION_CODES.M) + override fun onPageCommitVisible( + view: WebView?, + url: String?, + ) { + internalWebViewClient?.onPageCommitVisible(view, url) + } + + override fun shouldInterceptRequest( + view: WebView?, + url: String?, + ): WebResourceResponse? { + return internalWebViewClient?.shouldInterceptRequest(view, url) + } + + override fun shouldInterceptRequest( + view: WebView?, + request: WebResourceRequest?, + ): WebResourceResponse? { + return internalWebViewClient?.shouldInterceptRequest(view, request) + } + + override fun onReceivedError( + view: WebView?, + errorCode: Int, + description: String?, + failingUrl: String?, + ) { + internalWebViewClient?.onReceivedError(view, errorCode, description, failingUrl) + } + + @RequiresApi(Build.VERSION_CODES.M) + override fun onReceivedError( + view: WebView?, + request: WebResourceRequest?, + error: WebResourceError?, + ) { + internalWebViewClient?.onReceivedError(view, request, error) + } + + @RequiresApi(Build.VERSION_CODES.M) + override fun onReceivedHttpError( + view: WebView?, + request: WebResourceRequest?, + errorResponse: WebResourceResponse?, + ) { + internalWebViewClient?.onReceivedHttpError(view, request, errorResponse) + } + + override fun onFormResubmission( + view: WebView?, + dontResend: Message?, + resend: Message?, + ) { + internalWebViewClient?.onFormResubmission(view, dontResend, resend) + } + + override fun doUpdateVisitedHistory( + view: WebView?, + url: String?, + isReload: Boolean, + ) { + internalWebViewClient?.doUpdateVisitedHistory(view, url, isReload) + } + + override fun onReceivedSslError( + view: WebView?, + handler: SslErrorHandler?, + error: SslError?, + ) { + internalWebViewClient?.onReceivedSslError(view, handler, error) + } + + override fun onReceivedClientCertRequest( + view: WebView?, + request: ClientCertRequest?, + ) { + internalWebViewClient?.onReceivedClientCertRequest(view, request) + } + + override fun onReceivedHttpAuthRequest( + view: WebView?, + handler: HttpAuthHandler?, + host: String?, + realm: String?, + ) { + internalWebViewClient?.onReceivedHttpAuthRequest(view, handler, host, realm) + } + + override fun shouldOverrideKeyEvent( + view: WebView?, + event: KeyEvent?, + ): Boolean { + return internalWebViewClient?.shouldOverrideKeyEvent(view, event) ?: false + } + + override fun onUnhandledKeyEvent( + view: WebView?, + event: KeyEvent?, + ) { + internalWebViewClient?.onUnhandledKeyEvent(view, event) + } + + override fun onScaleChanged( + view: WebView?, + oldScale: Float, + newScale: Float, + ) { + internalWebViewClient?.onScaleChanged(view, oldScale, newScale) + } + + override fun onReceivedLoginRequest( + view: WebView?, + realm: String?, + account: String?, + args: String?, + ) { + internalWebViewClient?.onReceivedLoginRequest(view, realm, account, args) + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun onRenderProcessGone( + view: WebView?, + detail: RenderProcessGoneDetail?, + ): Boolean { + return internalWebViewClient?.onRenderProcessGone(view, detail) ?: false + } - init { - isFocusable = true - isFocusableInTouchMode = true - - settings.javaScriptEnabled = true - settings.domStorageEnabled = true - settings.databaseEnabled = true - - super.setWebViewClient(object : WebViewClient() { - override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { - url?.let { - if (!isMultipleWindowsSupported && url.isFitIllustratorURL) { - VirtusizeFitIllustratorFragment.launch(context, url) - return true - } - } - return _webViewClient?.shouldOverrideUrlLoading(view, url) ?: false - } - - @RequiresApi(Build.VERSION_CODES.N) - override fun shouldOverrideUrlLoading( - view: WebView?, - request: WebResourceRequest? - ): Boolean { - request?.let { - if (!isMultipleWindowsSupported && request.isFitIllustratorURL) { - VirtusizeFitIllustratorFragment.launch(context, request.urlString) - return true - } - } - return _webViewClient?.shouldOverrideUrlLoading(view, request) ?: false - } - - override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { - _webViewClient?.onPageStarted(view, url, favicon) - } - - override fun onPageFinished(view: WebView?, url: String?) { - // This is to close any subviews when a user is successfully logged into Facebook - if ( - isMultipleWindowsSupported && - url?.contains("virtusize") == true && - url.contains("#compare") - ) { - view?.removeAllViews() - } - _webViewClient?.onPageFinished(view, url) - } - - override fun onLoadResource(view: WebView?, url: String?) { - _webViewClient?.onLoadResource(view, url) - } - - @RequiresApi(Build.VERSION_CODES.M) - override fun onPageCommitVisible(view: WebView?, url: String?) { - _webViewClient?.onPageCommitVisible(view, url) - } - - override fun shouldInterceptRequest( - view: WebView?, - url: String? - ): WebResourceResponse? { - return _webViewClient?.shouldInterceptRequest(view, url) - } - - override fun shouldInterceptRequest( - view: WebView?, - request: WebResourceRequest? - ): WebResourceResponse? { - return _webViewClient?.shouldInterceptRequest(view, request) - } - - override fun onReceivedError( - view: WebView?, - errorCode: Int, - description: String?, - failingUrl: String? - ) { - _webViewClient?.onReceivedError(view, errorCode, description, failingUrl) - } - - @RequiresApi(Build.VERSION_CODES.M) - override fun onReceivedError( - view: WebView?, - request: WebResourceRequest?, - error: WebResourceError? - ) { - _webViewClient?.onReceivedError(view, request, error) - } - - @RequiresApi(Build.VERSION_CODES.M) - override fun onReceivedHttpError( - view: WebView?, - request: WebResourceRequest?, - errorResponse: WebResourceResponse? - ) { - _webViewClient?.onReceivedHttpError(view, request, errorResponse) - } - - override fun onFormResubmission( - view: WebView?, - dontResend: Message?, - resend: Message? - ) { - _webViewClient?.onFormResubmission(view, dontResend, resend) - } - - override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) { - _webViewClient?.doUpdateVisitedHistory(view, url, isReload) - } - - override fun onReceivedSslError( - view: WebView?, - handler: SslErrorHandler?, - error: SslError? - ) { - _webViewClient?.onReceivedSslError(view, handler, error) - } - - override fun onReceivedClientCertRequest(view: WebView?, request: ClientCertRequest?) { - _webViewClient?.onReceivedClientCertRequest(view, request) - } - - override fun onReceivedHttpAuthRequest( - view: WebView?, - handler: HttpAuthHandler?, - host: String?, - realm: String? - ) { - _webViewClient?.onReceivedHttpAuthRequest(view, handler, host, realm) - } - - override fun shouldOverrideKeyEvent(view: WebView?, event: KeyEvent?): Boolean { - return _webViewClient?.shouldOverrideKeyEvent(view, event) ?: false - } - - override fun onUnhandledKeyEvent(view: WebView?, event: KeyEvent?) { - _webViewClient?.onUnhandledKeyEvent(view, event) - } - - override fun onScaleChanged(view: WebView?, oldScale: Float, newScale: Float) { - _webViewClient?.onScaleChanged(view, oldScale, newScale) - } - - override fun onReceivedLoginRequest( - view: WebView?, - realm: String?, - account: String?, - args: String? - ) { - _webViewClient?.onReceivedLoginRequest(view, realm, account, args) - } - - @RequiresApi(Build.VERSION_CODES.O) - override fun onRenderProcessGone( - view: WebView?, - detail: RenderProcessGoneDetail? - ): Boolean { - return _webViewClient?.onRenderProcessGone(view, detail) ?: false - } - - @RequiresApi(Build.VERSION_CODES.O_MR1) - override fun onSafeBrowsingHit( - view: WebView?, - request: WebResourceRequest?, - threatType: Int, - callback: SafeBrowsingResponse? - ) { - _webViewClient?.onSafeBrowsingHit(view, request, threatType, callback) - } - }) - - super.setWebChromeClient(object : WebChromeClient() { - override fun onCreateWindow( - view: WebView, - dialog: Boolean, - userGesture: Boolean, - resultMsg: Message - ): Boolean { - if (resultMsg.obj != null && resultMsg.obj is WebView.WebViewTransport) { - val popupWebView = VirtusizeFitIllustratorWebView(view.context) - popupWebView.enableWindowsSettings() - popupWebView.layoutParams = LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT - ) - popupWebView.webViewClient = object : WebViewClient() { - override fun onPageFinished(view: WebView?, url: String?) { - // This is to scroll the webview to top when the fit illustrator is open - url?.let { - scrollTo(0, 0) + @RequiresApi(Build.VERSION_CODES.O_MR1) + override fun onSafeBrowsingHit( + view: WebView?, + request: WebResourceRequest?, + threatType: Int, + callback: SafeBrowsingResponse?, + ) { + internalWebViewClient?.onSafeBrowsingHit(view, request, threatType, callback) + } + }, + ) + + super.setWebChromeClient( + object : WebChromeClient() { + override fun onCreateWindow( + view: WebView, + dialog: Boolean, + userGesture: Boolean, + resultMsg: Message, + ): Boolean { + if (resultMsg.obj != null && resultMsg.obj is WebView.WebViewTransport) { + val popupWebView = VirtusizeFitIllustratorWebView(view.context) + popupWebView.enableWindowsSettings() + popupWebView.layoutParams = + LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + popupWebView.webViewClient = + object : WebViewClient() { + override fun onPageFinished( + view: WebView?, + url: String?, + ) { + // This is to scroll the webview to top when the fit illustrator is open + url?.let { + scrollTo(0, 0) + } + } + } + + popupWebView.webChromeClient = + object : WebChromeClient() { + override fun onCloseWindow(window: WebView) { + removeAllViews() + } + } + popupWebView.setOnKeyListener { v, keyCode, event -> + if (keyCode == KeyEvent.KEYCODE_BACK && + event.action == MotionEvent.ACTION_UP && + popupWebView.canGoBack() + ) { + popupWebView.goBack() + return@setOnKeyListener true + } + false } + addView(popupWebView) + val transport = resultMsg.obj as WebView.WebViewTransport + transport.webView = popupWebView + resultMsg.sendToTarget() + return true } + return internalWebChromeClient?.onCreateWindow( + view, + dialog, + userGesture, + resultMsg, + ) + ?: false } - popupWebView.webChromeClient = object : WebChromeClient() { - override fun onCloseWindow(window: WebView) { - removeAllViews() - } + override fun onProgressChanged( + view: WebView?, + newProgress: Int, + ) { + internalWebChromeClient?.onProgressChanged(view, newProgress) } - popupWebView.setOnKeyListener { v, keyCode, event -> - if (keyCode == KeyEvent.KEYCODE_BACK && - event.action == MotionEvent.ACTION_UP && - popupWebView.canGoBack() - ) { - popupWebView.goBack() - return@setOnKeyListener true - } - false - } - addView(popupWebView) - val transport = resultMsg.obj as WebView.WebViewTransport - transport.webView = popupWebView - resultMsg.sendToTarget() - return true - } - return _webChromeClient?.onCreateWindow(view, dialog, userGesture, resultMsg) - ?: false - } - - override fun onProgressChanged(view: WebView?, newProgress: Int) { - _webChromeClient?.onProgressChanged(view, newProgress) - } - - override fun onReceivedTitle(view: WebView?, title: String?) { - _webChromeClient?.onReceivedTitle(view, title) - } - - override fun onReceivedIcon(view: WebView?, icon: Bitmap?) { - _webChromeClient?.onReceivedIcon(view, icon) - } - - override fun onReceivedTouchIconUrl( - view: WebView?, - url: String?, - precomposed: Boolean - ) { - _webChromeClient?.onReceivedTouchIconUrl(view, url, precomposed) - } - - override fun onShowCustomView(view: View?, callback: CustomViewCallback?) { - _webChromeClient?.onShowCustomView(view, callback) - } - - override fun onHideCustomView() { - _webChromeClient?.onHideCustomView() - } - - override fun onRequestFocus(view: WebView?) { - _webChromeClient?.onRequestFocus(view) - } - - override fun onCloseWindow(window: WebView?) { - window?.removeAllViews() - _webChromeClient?.onCloseWindow(window) - } - - override fun onJsAlert( - view: WebView?, - url: String?, - message: String?, - result: JsResult? - ): Boolean { - return _webChromeClient?.onJsAlert(view, url, message, result) ?: false - } - - override fun onJsConfirm( - view: WebView?, - url: String?, - message: String?, - result: JsResult? - ): Boolean { - return _webChromeClient?.onJsConfirm(view, url, message, result) ?: false - } - - override fun onJsPrompt( - view: WebView?, - url: String?, - message: String?, - defaultValue: String?, - result: JsPromptResult? - ): Boolean { - return _webChromeClient?.onJsPrompt(view, url, message, defaultValue, result) - ?: false - } - - override fun onJsBeforeUnload( - view: WebView?, - url: String?, - message: String?, - result: JsResult? - ): Boolean { - return _webChromeClient?.onJsBeforeUnload(view, url, message, result) ?: false - } - - override fun onGeolocationPermissionsShowPrompt( - origin: String?, - callback: GeolocationPermissions.Callback? - ) { - _webChromeClient?.onGeolocationPermissionsShowPrompt(origin, callback) - } - - override fun onGeolocationPermissionsHidePrompt() { - _webChromeClient?.onGeolocationPermissionsHidePrompt() - } - - override fun onPermissionRequest(request: PermissionRequest?) { - _webChromeClient?.onPermissionRequest(request) - } - - override fun onPermissionRequestCanceled(request: PermissionRequest?) { - _webChromeClient?.onPermissionRequestCanceled(request) - } - - override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { - return _webChromeClient?.onConsoleMessage(consoleMessage) ?: false - } - - override fun onShowFileChooser( - webView: WebView?, - filePathCallback: ValueCallback>?, - fileChooserParams: FileChooserParams? - ): Boolean { - return _webChromeClient?.onShowFileChooser( - webView, - filePathCallback, - fileChooserParams - ) ?: false - } - - override fun getDefaultVideoPoster(): Bitmap? { - return _webChromeClient?.defaultVideoPoster - } - - override fun getVideoLoadingProgressView(): View? { - return _webChromeClient?.videoLoadingProgressView - } - - override fun getVisitedHistory(callback: ValueCallback>?) { - _webChromeClient?.getVisitedHistory(callback) - } - }) + + override fun onReceivedTitle( + view: WebView?, + title: String?, + ) { + internalWebChromeClient?.onReceivedTitle(view, title) + } + + override fun onReceivedIcon( + view: WebView?, + icon: Bitmap?, + ) { + internalWebChromeClient?.onReceivedIcon(view, icon) + } + + override fun onReceivedTouchIconUrl( + view: WebView?, + url: String?, + precomposed: Boolean, + ) { + internalWebChromeClient?.onReceivedTouchIconUrl(view, url, precomposed) + } + + override fun onShowCustomView( + view: View?, + callback: CustomViewCallback?, + ) { + internalWebChromeClient?.onShowCustomView(view, callback) + } + + override fun onHideCustomView() { + internalWebChromeClient?.onHideCustomView() + } + + override fun onRequestFocus(view: WebView?) { + internalWebChromeClient?.onRequestFocus(view) + } + + override fun onCloseWindow(window: WebView?) { + window?.removeAllViews() + internalWebChromeClient?.onCloseWindow(window) + } + + override fun onJsAlert( + view: WebView?, + url: String?, + message: String?, + result: JsResult?, + ): Boolean { + return internalWebChromeClient?.onJsAlert(view, url, message, result) ?: false + } + + override fun onJsConfirm( + view: WebView?, + url: String?, + message: String?, + result: JsResult?, + ): Boolean { + return internalWebChromeClient?.onJsConfirm(view, url, message, result) ?: false + } + + override fun onJsPrompt( + view: WebView?, + url: String?, + message: String?, + defaultValue: String?, + result: JsPromptResult?, + ): Boolean { + return internalWebChromeClient?.onJsPrompt( + view, + url, + message, + defaultValue, + result, + ) + ?: false + } + + override fun onJsBeforeUnload( + view: WebView?, + url: String?, + message: String?, + result: JsResult?, + ): Boolean { + return internalWebChromeClient?.onJsBeforeUnload( + view, + url, + message, + result, + ) ?: false + } + + override fun onGeolocationPermissionsShowPrompt( + origin: String?, + callback: GeolocationPermissions.Callback?, + ) { + internalWebChromeClient?.onGeolocationPermissionsShowPrompt(origin, callback) + } + + override fun onGeolocationPermissionsHidePrompt() { + internalWebChromeClient?.onGeolocationPermissionsHidePrompt() + } + + override fun onPermissionRequest(request: PermissionRequest?) { + internalWebChromeClient?.onPermissionRequest(request) + } + + override fun onPermissionRequestCanceled(request: PermissionRequest?) { + internalWebChromeClient?.onPermissionRequestCanceled(request) + } + + override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { + return internalWebChromeClient?.onConsoleMessage(consoleMessage) ?: false + } + + override fun onShowFileChooser( + webView: WebView?, + filePathCallback: ValueCallback>?, + fileChooserParams: FileChooserParams?, + ): Boolean { + return internalWebChromeClient?.onShowFileChooser( + webView, + filePathCallback, + fileChooserParams, + ) ?: false + } + + override fun getDefaultVideoPoster(): Bitmap? { + return internalWebChromeClient?.defaultVideoPoster + } + + override fun getVideoLoadingProgressView(): View? { + return internalWebChromeClient?.videoLoadingProgressView + } + + override fun getVisitedHistory(callback: ValueCallback>?) { + internalWebChromeClient?.getVisitedHistory(callback) + } + }, + ) + } } -} diff --git a/virtusize/src/main/java/com/virtusize/android/VirtusizePresenter.kt b/virtusize/src/main/java/com/virtusize/android/VirtusizePresenter.kt index a9539c95..7f9ab9da 100644 --- a/virtusize/src/main/java/com/virtusize/android/VirtusizePresenter.kt +++ b/virtusize/src/main/java/com/virtusize/android/VirtusizePresenter.kt @@ -7,10 +7,15 @@ import com.virtusize.android.data.local.VirtusizeProduct // An interface to pass the data from the actions of VirtusizeRepository internal interface VirtusizePresenter { fun onValidProductDataCheck(productWithPDCData: VirtusizeProduct) + fun gotSizeRecommendations( externalProductId: String, userProductRecommendedSize: SizeComparisonRecommendedSize?, - userBodyRecommendedSize: String? + userBodyRecommendedSize: String?, + ) + + fun hasInPageError( + externalProductId: String?, + error: VirtusizeError?, ) - fun hasInPageError(externalProductId: String?, error: VirtusizeError?) } diff --git a/virtusize/src/main/java/com/virtusize/android/VirtusizeRepository.kt b/virtusize/src/main/java/com/virtusize/android/VirtusizeRepository.kt index 98112a0d..72854bfb 100644 --- a/virtusize/src/main/java/com/virtusize/android/VirtusizeRepository.kt +++ b/virtusize/src/main/java/com/virtusize/android/VirtusizeRepository.kt @@ -30,9 +30,8 @@ import java.net.HttpURLConnection internal class VirtusizeRepository( private val context: Context, private var messageHandler: VirtusizeMessageHandler, - private var presenter: VirtusizePresenter? = null + private var presenter: VirtusizePresenter? = null, ) { - // This variable is the instance of VirtusizeAPIService to handle Virtusize API requests private var virtusizeAPIService = VirtusizeAPIService.getInstance(context, messageHandler) @@ -91,7 +90,7 @@ internal class VirtusizeRepository( // Send API Event UserSawProduct sendEvent( virtusizeProduct, - VirtusizeEvent(VirtusizeEvents.UserSawProduct.getEventName()) + VirtusizeEvent(VirtusizeEvents.UserSawProduct.getEventName()), ) productCheck.data?.apply { @@ -101,12 +100,12 @@ internal class VirtusizeRepository( // If image URL is valid, send image URL to server val sendProductImageResponse = virtusizeAPIService.sendProductImageToBackend( - product = virtusizeProduct + product = virtusizeProduct, ) if (!sendProductImageResponse.isSuccessful) { sendProductImageResponse.failureData?.let { messageHandler.onError( - it + it, ) } } @@ -118,7 +117,7 @@ internal class VirtusizeRepository( // Send API Event UserSawWidgetButton sendEvent( virtusizeProduct, - VirtusizeEvent(VirtusizeEvents.UserSawWidgetButton.getEventName()) + VirtusizeEvent(VirtusizeEvents.UserSawWidgetButton.getEventName()), ) presenter?.onValidProductDataCheck(virtusizeProduct) @@ -126,8 +125,8 @@ internal class VirtusizeRepository( presenter?.hasInPageError( virtusizeProduct.externalId, VirtusizeErrorType.InvalidProduct.virtusizeError( - extraMessage = virtusizeProduct.externalId - ) + extraMessage = virtusizeProduct.externalId, + ), ) } } @@ -141,11 +140,15 @@ internal class VirtusizeRepository( * @param product the [VirtusizeProduct] data wit the product check data * @param vsEvent the [VirtusizeEvent] */ - private suspend fun sendEvent(product: VirtusizeProduct, vsEvent: VirtusizeEvent) { - val sendEventResponse = virtusizeAPIService.sendEvent( - event = vsEvent, - withDataProduct = product.productCheckData - ) + private suspend fun sendEvent( + product: VirtusizeProduct, + vsEvent: VirtusizeEvent, + ) { + val sendEventResponse = + virtusizeAPIService.sendEvent( + event = vsEvent, + withDataProduct = product.productCheckData, + ) if (sendEventResponse.isSuccessful) { messageHandler.onEvent(product, vsEvent) } @@ -158,7 +161,7 @@ internal class VirtusizeRepository( */ internal suspend fun fetchInitialData( language: VirtusizeLanguage?, - product: VirtusizeProduct + product: VirtusizeProduct, ) { val productId = product.productCheckData!!.data!!.productDataId val externalProductId = product.externalId @@ -192,20 +195,18 @@ internal class VirtusizeRepository( * Updates the user session by calling the session API * @param externalProductId the external product ID set by a client */ - internal suspend fun updateUserSession( - externalProductId: String? = lastProductOnVirtusizeWebView?.externalId - ) { + internal suspend fun updateUserSession(externalProductId: String? = lastProductOnVirtusizeWebView?.externalId) { val userSessionInfoResponse = virtusizeAPIService.getUserSessionInfo() if (userSessionInfoResponse.isSuccessful) { sharedPreferencesHelper.storeSessionData( - userSessionInfoResponse.successData!!.userSessionResponse + userSessionInfoResponse.successData!!.userSessionResponse, ) sharedPreferencesHelper.storeAccessToken( - userSessionInfoResponse.successData!!.accessToken + userSessionInfoResponse.successData!!.accessToken, ) if (userSessionInfoResponse.successData!!.authToken.isNotBlank()) { sharedPreferencesHelper.storeAuthToken( - userSessionInfoResponse.successData!!.authToken + userSessionInfoResponse.successData!!.authToken, ) } } else { @@ -224,7 +225,7 @@ internal class VirtusizeRepository( externalProductId: String? = null, selectedUserProductId: Int? = null, shouldUpdateUserProducts: Boolean = true, - shouldUpdateBodyProfile: Boolean = true + shouldUpdateBodyProfile: Boolean = true, ) { var storeProduct = lastProductOnVirtusizeWebView externalProductId?.let { @@ -239,7 +240,7 @@ internal class VirtusizeRepository( } else if (userProductsResponse.failureData?.code != HttpURLConnection.HTTP_NOT_FOUND) { presenter?.hasInPageError( storeProduct?.externalId, - userProductsResponse.failureData + userProductsResponse.failureData, ) return } @@ -249,15 +250,17 @@ internal class VirtusizeRepository( userBodyRecommendedSize = getUserBodyRecommendedSize(storeProduct, productTypes) } - userProductRecommendedSize = VirtusizeUtils.findBestFitProductSize( - userProducts = - if (selectedUserProductId != null) - userProducts?.filter { it.id == selectedUserProductId } - else - userProducts, - storeProduct = storeProduct, - productTypes = productTypes - ) + userProductRecommendedSize = + VirtusizeUtils.findBestFitProductSize( + userProducts = + if (selectedUserProductId != null) { + userProducts?.filter { it.id == selectedUserProductId } + } else { + userProducts + }, + storeProduct = storeProduct, + productTypes = productTypes, + ) } /** @@ -283,29 +286,29 @@ internal class VirtusizeRepository( */ internal fun updateInPageRecommendation( externalProductId: String? = null, - type: SizeRecommendationType? = null + type: SizeRecommendationType? = null, ) { (externalProductId ?: lastProductOnVirtusizeWebView?.externalId)?.let { externalProductId -> when (type) { - SizeRecommendationType.compareProduct -> { + SizeRecommendationType.CompareProduct -> { presenter?.gotSizeRecommendations( externalProductId, userProductRecommendedSize, - null + null, ) } - SizeRecommendationType.body -> { + SizeRecommendationType.Body -> { presenter?.gotSizeRecommendations( externalProductId, null, - userBodyRecommendedSize + userBodyRecommendedSize, ) } else -> { presenter?.gotSizeRecommendations( externalProductId, userProductRecommendedSize, - userBodyRecommendedSize + userBodyRecommendedSize, ) } } @@ -332,7 +335,7 @@ internal class VirtusizeRepository( */ private suspend fun getUserBodyRecommendedSize( storeProduct: Product?, - productTypes: List? + productTypes: List?, ): String? { if (storeProduct == null || productTypes == null || storeProduct.isAccessory()) { return null @@ -343,7 +346,7 @@ internal class VirtusizeRepository( virtusizeAPIService.getBodyProfileRecommendedSize( productTypes, storeProduct, - userBodyProfileResponse.successData!! + userBodyProfileResponse.successData!!, ) return bodyProfileRecommendedSizeResponse.successData?.get(0)?.sizeName } else if (userBodyProfileResponse.failureData?.code != HttpURLConnection.HTTP_NOT_FOUND) { @@ -365,7 +368,7 @@ internal class VirtusizeRepository( params: VirtusizeParams, order: VirtusizeOrder, onSuccess: ((Any?) -> Unit)?, - onError: ((VirtusizeError) -> Unit)? + onError: ((VirtusizeError) -> Unit)?, ) { // Throws the error if the user id is not set up or empty if (params.externalUserId.isNullOrEmpty()) { @@ -390,8 +393,7 @@ internal class VirtusizeRepository( * @param urlString the image URL string * @return the bitmap of the image */ - internal suspend fun loadImage(urlString: String?): Bitmap? = - if (urlString == null) null else virtusizeAPIService.loadImage(urlString) + internal suspend fun loadImage(urlString: String?): Bitmap? = if (urlString == null) null else virtusizeAPIService.loadImage(urlString) /** * Updates the browser ID and the auth token from the data of the event user-auth-data @@ -405,8 +407,8 @@ internal class VirtusizeRepository( } catch (e: JSONException) { messageHandler.onError( VirtusizeErrorType.JsonParsingError.virtusizeError( - extraMessage = e.localizedMessage - ) + extraMessage = e.localizedMessage, + ), ) } } diff --git a/virtusize/src/main/java/com/virtusize/android/data/local/ProductComparisonFitInfo.kt b/virtusize/src/main/java/com/virtusize/android/data/local/ProductComparisonFitInfo.kt index c7ef0260..6e484fc2 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/local/ProductComparisonFitInfo.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/local/ProductComparisonFitInfo.kt @@ -7,5 +7,5 @@ package com.virtusize.android.data.local */ internal data class ProductComparisonFitInfo( val fitScore: Float, - var isSmaller: Boolean? + var isSmaller: Boolean?, ) diff --git a/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeInfoCategory.kt b/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeInfoCategory.kt index 25409e34..25335895 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeInfoCategory.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeInfoCategory.kt @@ -7,5 +7,5 @@ enum class VirtusizeInfoCategory(val value: String) { MODEL_INFO("modelInfo"), GENERAL_FIT("generalFit"), BRAND_SIZING("brandSizing"), - MATERIAL("material") + MATERIAL("material"), } diff --git a/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeParams.kt b/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeParams.kt index 5e08c3ef..0ffcb2eb 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeParams.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeParams.kt @@ -25,9 +25,8 @@ data class VirtusizeParams( private val allowedLanguages: MutableList, internal var externalUserId: String?, private val showSGI: Boolean, - private val detailsPanelCards: MutableList + private val detailsPanelCards: MutableList, ) { - /** * Returns the virtusize parameter string to be passed to the JavaScript function vsParamsFromSDK */ diff --git a/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeViewStyle.kt b/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeViewStyle.kt index 801df1ce..0a4bd4e3 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeViewStyle.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/local/VirtusizeViewStyle.kt @@ -6,5 +6,5 @@ package com.virtusize.android.data.local enum class VirtusizeViewStyle(val value: Int) { NONE(-1), BLACK(0), - TEAL(1) + TEAL(1), } 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 5bbd135d..9e2a0c9c 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 @@ -13,7 +13,6 @@ import org.json.JSONObject */ internal class BodyProfileRecommendedSizeJsonParser(private val product: Product) : VirtusizeJsonParser { - override fun parse(json: JSONObject): BodyProfileRecommendedSizeNew? { val sizeName = json.getString(FIELD_NAME) val extProductId = json.optString(EXT_PRODUCT_ID, "") @@ -28,23 +27,25 @@ internal class BodyProfileRecommendedSizeJsonParser(private val product: Product val willFit = json.optBoolean(WILL_FIT, false) var virtualItem: VirtualItem? = null virtualItemJsonObj?.let { - virtualItem = VirtualItem( - bust = it.optDouble(BUST, 0.0), - hip = it.optDouble(HIP, 0.0), - inseam = it.optDouble(INSEAM, 0.0), - sleeve = it.optDouble(SLEEVE, 0.0), - waist = it.optDouble(WAIST, 0.0), - ) + virtualItem = + VirtualItem( + bust = it.optDouble(BUST, 0.0), + hip = it.optDouble(HIP, 0.0), + inseam = it.optDouble(INSEAM, 0.0), + sleeve = it.optDouble(SLEEVE, 0.0), + waist = it.optDouble(WAIST, 0.0), + ) } var willFitForSizes: WillFitForSizes? = null willFitForSizesJsonObj?.let { - willFitForSizes = WillFitForSizes( - extra_large = it.optBoolean(EXTRA_LARGE, false), - extra_small = it.optBoolean(EXTRA_SMALL, false), - large = it.optBoolean(LARGE, false), - medium = it.optBoolean(MEDIUM, false), - small = it.optBoolean(SMALL, false), - ) + willFitForSizes = + WillFitForSizes( + extra_large = it.optBoolean(EXTRA_LARGE, false), + extra_small = it.optBoolean(EXTRA_SMALL, false), + large = it.optBoolean(LARGE, false), + medium = it.optBoolean(MEDIUM, false), + small = it.optBoolean(SMALL, false), + ) } return BodyProfileRecommendedSizeNew( @@ -58,7 +59,7 @@ internal class BodyProfileRecommendedSizeJsonParser(private val product: Product thresholdFitScore = thresholdFitScore, willFit = willFit, virtualItem = virtualItem, - willFitForSizes = willFitForSizes + willFitForSizes = willFitForSizes, ) } diff --git a/virtusize/src/main/java/com/virtusize/android/data/parsers/I18nLocalizationJsonParser.kt b/virtusize/src/main/java/com/virtusize/android/data/parsers/I18nLocalizationJsonParser.kt index ed266a4e..fd780221 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/parsers/I18nLocalizationJsonParser.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/parsers/I18nLocalizationJsonParser.kt @@ -12,109 +12,122 @@ import org.json.JSONObject */ internal class I18nLocalizationJsonParser( val context: Context, - private val virtusizeLanguage: VirtusizeLanguage? + private val virtusizeLanguage: VirtusizeLanguage?, ) : VirtusizeJsonParser { - override fun parse(json: JSONObject): I18nLocalization { - val aoyamaJSONObject = json.optJSONObject(FIELD_KEYS) - ?.optJSONObject(FIELD_APPS) - ?.optJSONObject(FIELD_AOYAMA) + val aoyamaJSONObject = + json.optJSONObject(FIELD_KEYS) + ?.optJSONObject(FIELD_APPS) + ?.optJSONObject(FIELD_AOYAMA) val inpageJSONObject = aoyamaJSONObject?.optJSONObject(FIELD_INPAGE) val oneSizeJSONObject = inpageJSONObject?.optJSONObject(FIELD_ONE_SIZE) val multiSizeJSONObject = inpageJSONObject?.optJSONObject(FIELD_MULTI_SIZE) val accessoryJSONObject = inpageJSONObject?.optJSONObject(FIELD_ACCESSORY) val configuredContext = VirtusizeUtils.getConfiguredContext(context, virtusizeLanguage) - val defaultAccessoryText = inpageJSONObject?.optString( - FIELD_DEFAULT_ACCESSORY_TEXT, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_default_accessory_text + val defaultAccessoryText = + inpageJSONObject?.optString( + FIELD_DEFAULT_ACCESSORY_TEXT, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_default_accessory_text, + ) ?: "", ) ?: "" - ) ?: "" - val hasProductAccessoryTopText = accessoryJSONObject?.optString( - FIELD_HAS_PRODUCT_LEAD, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_has_product_top_text + val hasProductAccessoryTopText = + accessoryJSONObject?.optString( + FIELD_HAS_PRODUCT_LEAD, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_has_product_top_text, + ) ?: "", ) ?: "" - ) ?: "" - val hasProductAccessoryBottomText = accessoryJSONObject?.optString( - FIELD_HAS_PRODUCT, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_has_product_bottom_text + val hasProductAccessoryBottomText = + accessoryJSONObject?.optString( + FIELD_HAS_PRODUCT, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_has_product_bottom_text, + ) ?: "", ) ?: "" - ) ?: "" - val oneSizeCloseTopText = oneSizeJSONObject?.optString( - FIELD_CLOSE_LEAD, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_one_size_close_top_text + val oneSizeCloseTopText = + oneSizeJSONObject?.optString( + FIELD_CLOSE_LEAD, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_one_size_close_top_text, + ) ?: "", ) ?: "" - ) ?: "" - val oneSizeSmallerTopText = oneSizeJSONObject?.optString( - FIELD_SMALLER_LEAD, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_one_size_smaller_top_text + val oneSizeSmallerTopText = + oneSizeJSONObject?.optString( + FIELD_SMALLER_LEAD, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_one_size_smaller_top_text, + ) ?: "", ) ?: "" - ) ?: "" - val oneSizeLargerTopText = oneSizeJSONObject?.optString( - FIELD_LARGER_LEAD, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_one_size_larger_top_text + val oneSizeLargerTopText = + oneSizeJSONObject?.optString( + FIELD_LARGER_LEAD, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_one_size_larger_top_text, + ) ?: "", ) ?: "" - ) ?: "" - val oneSizeCloseBottomText = oneSizeJSONObject?.optString( - FIELD_CLOSE, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_one_size_close_bottom_text + val oneSizeCloseBottomText = + oneSizeJSONObject?.optString( + FIELD_CLOSE, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_one_size_close_bottom_text, + ) ?: "", ) ?: "" - ) ?: "" - val oneSizeSmallerBottomText = oneSizeJSONObject?.optString( - FIELD_SMALLER, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_one_size_smaller_bottom_text + val oneSizeSmallerBottomText = + oneSizeJSONObject?.optString( + FIELD_SMALLER, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_one_size_smaller_bottom_text, + ) ?: "", ) ?: "" - ) ?: "" - val oneSizeLargerBottomText = oneSizeJSONObject?.optString( - FIELD_LARGER, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_one_size_larger_bottom_text + val oneSizeLargerBottomText = + oneSizeJSONObject?.optString( + FIELD_LARGER, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_one_size_larger_bottom_text, + ) ?: "", ) ?: "" - ) ?: "" - val bodyProfileOneSizeText = oneSizeJSONObject?.optString( - FIELD_BODY_PROFILE, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_one_size_body_profile_text + val bodyProfileOneSizeText = + oneSizeJSONObject?.optString( + FIELD_BODY_PROFILE, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_one_size_body_profile_text, + ) ?: "", ) ?: "" - ) ?: "" - val sizeComparisonMultiSizeText = multiSizeJSONObject?.optString( - FIELD_SIZE_COMPARISON, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_multi_size_comparison_text + val sizeComparisonMultiSizeText = + multiSizeJSONObject?.optString( + FIELD_SIZE_COMPARISON, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_multi_size_comparison_text, + ) ?: "", ) ?: "" - ) ?: "" - val bodyProfileMultiSizeText = multiSizeJSONObject?.optString( - FIELD_BODY_PROFILE, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_multi_size_body_profile_text + val bodyProfileMultiSizeText = + multiSizeJSONObject?.optString( + FIELD_BODY_PROFILE, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_multi_size_body_profile_text, + ) ?: "", ) ?: "" - ) ?: "" - val defaultNoDataText = inpageJSONObject?.optString( - FIELD_NO_DATA_TEXT, - configuredContext?.getString( - com.virtusize.android.core.R.string.inpage_no_data_text + val defaultNoDataText = + inpageJSONObject?.optString( + FIELD_NO_DATA_TEXT, + configuredContext?.getString( + com.virtusize.android.core.R.string.inpage_no_data_text, + ) ?: "", ) ?: "" - ) ?: "" return I18nLocalization( defaultAccessoryText, @@ -129,7 +142,7 @@ internal class I18nLocalizationJsonParser( bodyProfileOneSizeText, sizeComparisonMultiSizeText, bodyProfileMultiSizeText, - defaultNoDataText + defaultNoDataText, ) } diff --git a/virtusize/src/main/java/com/virtusize/android/data/parsers/StoreJsonParser.kt b/virtusize/src/main/java/com/virtusize/android/data/parsers/StoreJsonParser.kt index ec473312..b43d8fdf 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/parsers/StoreJsonParser.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/parsers/StoreJsonParser.kt @@ -31,7 +31,7 @@ internal class StoreJsonParser : VirtusizeJsonParser { updated, disabled, typeMapperEnabled, - region + region, ) } diff --git a/virtusize/src/main/java/com/virtusize/android/data/parsers/StoreProductAdditionalInfoJsonParser.kt b/virtusize/src/main/java/com/virtusize/android/data/parsers/StoreProductAdditionalInfoJsonParser.kt index 79b1d595..29f01275 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/parsers/StoreProductAdditionalInfoJsonParser.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/parsers/StoreProductAdditionalInfoJsonParser.kt @@ -16,16 +16,17 @@ internal class StoreProductAdditionalInfoJsonParser : val gender = JsonUtils.optNullableString(json, FIELD_GENDER) var sizes = setOf() json.optJSONObject(FIELD_SIZES)?.let { jsonObject -> - sizes = JsonUtils.jsonObjectToMap(jsonObject) - .map { filteredSizeHashMap -> - ProductSize( - filteredSizeHashMap.key, - (filteredSizeHashMap.value as MutableMap) - .map { - Measurement(it.key, it.value) - }.toMutableSet() - ) - }.toMutableSet() + sizes = + JsonUtils.jsonObjectToMap(jsonObject) + .map { filteredSizeHashMap -> + ProductSize( + filteredSizeHashMap.key, + (filteredSizeHashMap.value as MutableMap) + .map { + Measurement(it.key, it.value) + }.toMutableSet(), + ) + }.toMutableSet() } var modelInfo = mutableMapOf() json.optJSONObject(FIELD_MODEL_INFO)?.let { diff --git a/virtusize/src/main/java/com/virtusize/android/data/parsers/UserAuthDataJsonParser.kt b/virtusize/src/main/java/com/virtusize/android/data/parsers/UserAuthDataJsonParser.kt index 9e33e32e..5f2a41a6 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/parsers/UserAuthDataJsonParser.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/parsers/UserAuthDataJsonParser.kt @@ -4,7 +4,6 @@ import com.virtusize.android.data.remote.UserAuthData import org.json.JSONObject internal class UserAuthDataJsonParser : VirtusizeJsonParser { - override fun parse(json: JSONObject): UserAuthData? { val bid = json.optString(FIELD_VS_BID) val authHeader = json.optString(FIELD_VS_AUTH) diff --git a/virtusize/src/main/java/com/virtusize/android/data/remote/BodyProfileRecommendedSize.kt b/virtusize/src/main/java/com/virtusize/android/data/remote/BodyProfileRecommendedSize.kt index 86c6fa22..29d62e52 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/remote/BodyProfileRecommendedSize.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/remote/BodyProfileRecommendedSize.kt @@ -7,5 +7,5 @@ package com.virtusize.android.data.remote */ data class BodyProfileRecommendedSize( val product: Product, - internal val sizeName: String + internal val sizeName: String, ) diff --git a/virtusize/src/main/java/com/virtusize/android/data/remote/BodyProfileRecommendedSizeNew.kt b/virtusize/src/main/java/com/virtusize/android/data/remote/BodyProfileRecommendedSizeNew.kt index 2030b85d..ce8a2aae 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/remote/BodyProfileRecommendedSizeNew.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/remote/BodyProfileRecommendedSizeNew.kt @@ -11,5 +11,5 @@ data class BodyProfileRecommendedSizeNew( val thresholdFitScore: Double, val virtualItem: VirtualItem?, val willFit: Boolean, - val willFitForSizes: WillFitForSizes? + val willFitForSizes: WillFitForSizes?, ) diff --git a/virtusize/src/main/java/com/virtusize/android/data/remote/ProductMetaDataHints.kt b/virtusize/src/main/java/com/virtusize/android/data/remote/ProductMetaDataHints.kt index e3eeb709..3b4d5a65 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/remote/ProductMetaDataHints.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/remote/ProductMetaDataHints.kt @@ -8,5 +8,5 @@ internal data class ProductMetaDataHints( val apiKey: String, val imageUrl: String, val cloudinaryPublicId: String, - val externalProductId: String + val externalProductId: String, ) diff --git a/virtusize/src/main/java/com/virtusize/android/data/remote/Store.kt b/virtusize/src/main/java/com/virtusize/android/data/remote/Store.kt index 2d39faa8..b343b8bd 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/remote/Store.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/remote/Store.kt @@ -14,5 +14,5 @@ internal data class Store( val updated: String, val disabled: String, val typeMapperEnabled: Boolean, - val region: String + val region: String, ) diff --git a/virtusize/src/main/java/com/virtusize/android/data/remote/UserAuthData.kt b/virtusize/src/main/java/com/virtusize/android/data/remote/UserAuthData.kt index 13fe6880..241202a4 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/remote/UserAuthData.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/remote/UserAuthData.kt @@ -7,5 +7,5 @@ package com.virtusize.android.data.remote */ internal data class UserAuthData( val bid: String, - val auth: String + val auth: String, ) diff --git a/virtusize/src/main/java/com/virtusize/android/data/remote/VirtualItem.kt b/virtusize/src/main/java/com/virtusize/android/data/remote/VirtualItem.kt index 32d2f878..93527d68 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/remote/VirtualItem.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/remote/VirtualItem.kt @@ -5,5 +5,5 @@ data class VirtualItem( val hip: Double, val inseam: Double, val sleeve: Double, - val waist: Double + val waist: Double, ) diff --git a/virtusize/src/main/java/com/virtusize/android/data/remote/WillFitForSizes.kt b/virtusize/src/main/java/com/virtusize/android/data/remote/WillFitForSizes.kt index a0b3c26d..a25161ff 100644 --- a/virtusize/src/main/java/com/virtusize/android/data/remote/WillFitForSizes.kt +++ b/virtusize/src/main/java/com/virtusize/android/data/remote/WillFitForSizes.kt @@ -5,5 +5,5 @@ data class WillFitForSizes( val extra_small: Boolean, val large: Boolean, val medium: Boolean, - val small: Boolean + val small: Boolean, ) diff --git a/virtusize/src/main/java/com/virtusize/android/flutter/VirtusizeFlutterRepository.kt b/virtusize/src/main/java/com/virtusize/android/flutter/VirtusizeFlutterRepository.kt index 62b8e701..cdf3dd7c 100644 --- a/virtusize/src/main/java/com/virtusize/android/flutter/VirtusizeFlutterRepository.kt +++ b/virtusize/src/main/java/com/virtusize/android/flutter/VirtusizeFlutterRepository.kt @@ -27,7 +27,7 @@ import java.net.HttpURLConnection class VirtusizeFlutterRepository( context: Context, - private val messageHandler: VirtusizeMessageHandler + private val messageHandler: VirtusizeMessageHandler, ) { private val apiService: VirtusizeAPIService = VirtusizeAPIService.getInstance(context, messageHandler) @@ -42,13 +42,13 @@ class VirtusizeFlutterRepository( private suspend fun sendEventsAndProductImage( product: VirtusizeProduct, - pdcResponse: VirtusizeApiResponse + pdcResponse: VirtusizeApiResponse, ) { if (pdcResponse.isSuccessful) { product.productCheckData = pdcResponse.successData sendEvent( product, - VirtusizeEvent(VirtusizeEvents.UserSawProduct.getEventName()) + VirtusizeEvent(VirtusizeEvents.UserSawProduct.getEventName()), ) val productCheck = pdcResponse.successData productCheck?.data?.apply { @@ -61,7 +61,7 @@ class VirtusizeFlutterRepository( if (!sendProductImageResponse.isSuccessful) { sendProductImageResponse.failureData?.let { messageHandler.onError( - it + it, ) } } @@ -72,18 +72,22 @@ class VirtusizeFlutterRepository( sendEvent( product, - VirtusizeEvent(VirtusizeEvents.UserSawWidgetButton.getEventName()) + VirtusizeEvent(VirtusizeEvents.UserSawWidgetButton.getEventName()), ) } } } } - private suspend fun sendEvent(product: VirtusizeProduct, vsEvent: VirtusizeEvent) { - val sendEventResponse = apiService.sendEvent( - event = vsEvent, - withDataProduct = product.productCheckData - ) + private suspend fun sendEvent( + product: VirtusizeProduct, + vsEvent: VirtusizeEvent, + ) { + val sendEventResponse = + apiService.sendEvent( + event = vsEvent, + withDataProduct = product.productCheckData, + ) if (sendEventResponse.isSuccessful) { messageHandler.onEvent(product, vsEvent) } @@ -93,8 +97,7 @@ class VirtusizeFlutterRepository( suspend fun getProductTypes() = apiService.getProductTypes().successData - suspend fun getI18nLocalization(language: VirtusizeLanguage?) = - apiService.getI18n(language).successData + suspend fun getI18nLocalization(language: VirtusizeLanguage?) = apiService.getI18n(language).successData suspend fun getUserSessionResponse(): String? { val userSessionInfo = apiService.getUserSessionInfo().successData @@ -116,8 +119,8 @@ class VirtusizeFlutterRepository( } catch (e: JSONException) { messageHandler.onError( VirtusizeErrorType.JsonParsingError.virtusizeError( - extraMessage = e.localizedMessage - ) + extraMessage = e.localizedMessage, + ), ) } } @@ -137,11 +140,11 @@ class VirtusizeFlutterRepository( suspend fun getBodyProfileRecommendedSize( productTypes: List, storeProduct: Product, - userBodyProfile: UserBodyProfile + userBodyProfile: UserBodyProfile, ) = apiService.getBodyProfileRecommendedSize( productTypes, storeProduct, - userBodyProfile + userBodyProfile, ).successData suspend fun deleteUser(): Any? { @@ -156,7 +159,7 @@ class VirtusizeFlutterRepository( virtusize: Virtusize?, orderMap: Map, onSuccess: ((Any?) -> Unit)?, - onError: ((VirtusizeError) -> Unit)? + onError: ((VirtusizeError) -> Unit)?, ) { if (virtusize?.params?.externalUserId.isNullOrEmpty()) { VirtusizeErrorType.UserIdNullOrEmpty.throwError() @@ -166,7 +169,7 @@ class VirtusizeFlutterRepository( val sendOrderResponse = apiService.sendOrder( storeInfoResponse.successData?.region, - VirtusizeOrder.parseMap(orderMap) + VirtusizeOrder.parseMap(orderMap), ) if (sendOrderResponse.isSuccessful) { onSuccess?.invoke(sendOrderResponse.successData) diff --git a/virtusize/src/main/java/com/virtusize/android/flutter/VirtusizeFlutterUtils.kt b/virtusize/src/main/java/com/virtusize/android/flutter/VirtusizeFlutterUtils.kt index f86d417c..09796bd2 100644 --- a/virtusize/src/main/java/com/virtusize/android/flutter/VirtusizeFlutterUtils.kt +++ b/virtusize/src/main/java/com/virtusize/android/flutter/VirtusizeFlutterUtils.kt @@ -18,19 +18,18 @@ import com.virtusize.android.util.VirtusizeUtils import com.virtusize.android.util.trimI18nText object VirtusizeFlutterUtils { - fun openVirtusizeView( activity: Activity, virtusize: Virtusize?, product: VirtusizeProduct, - messageHandler: VirtusizeMessageHandler + messageHandler: VirtusizeMessageHandler, ) { VirtusizeUtils.openVirtusizeWebView( activity, virtusize?.params, VirtusizeWebViewFragment(), product, - messageHandler + messageHandler, ) } @@ -38,15 +37,16 @@ object VirtusizeFlutterUtils { selectedRecommendedType: SizeRecommendationType? = null, userProducts: List?, storeProduct: Product, - productTypes: List + productTypes: List, ): SizeComparisonRecommendedSize? { var userProductRecommendedSize: SizeComparisonRecommendedSize? = null - if (selectedRecommendedType != SizeRecommendationType.body) { - userProductRecommendedSize = VirtusizeUtils.findBestFitProductSize( - userProducts = userProducts, - storeProduct = storeProduct, - productTypes = productTypes - ) + if (selectedRecommendedType != SizeRecommendationType.Body) { + userProductRecommendedSize = + VirtusizeUtils.findBestFitProductSize( + userProducts = userProducts, + storeProduct = storeProduct, + productTypes = productTypes, + ) } return userProductRecommendedSize } @@ -56,26 +56,30 @@ object VirtusizeFlutterUtils { storeProduct: Product, userProductRecommendedSize: SizeComparisonRecommendedSize?, bodyProfileRecommendedSize: BodyProfileRecommendedSize?, - i18nLocalization: I18nLocalization + i18nLocalization: I18nLocalization, ): String { var userBodyRecommendedSize: String? = null - if (selectedRecommendedType != SizeRecommendationType.compareProduct) { + if (selectedRecommendedType != SizeRecommendationType.CompareProduct) { userBodyRecommendedSize = bodyProfileRecommendedSize?.sizeName } return storeProduct.getRecommendationText( i18nLocalization, userProductRecommendedSize, - userBodyRecommendedSize + userBodyRecommendedSize, ).trimI18nText(I18nLocalization.TrimType.MULTIPLELINES) } - fun getPrivacyPolicyLink(context: Context, language: VirtusizeLanguage?): String? { - val configuredContext = VirtusizeUtils.getConfiguredContext( - context, - language - ) + fun getPrivacyPolicyLink( + context: Context, + language: VirtusizeLanguage?, + ): String? { + val configuredContext = + VirtusizeUtils.getConfiguredContext( + context, + language, + ) return configuredContext?.getString(R.string.virtusize_privacy_policy_link) } } 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 9c1e67f1..4329e699 100644 --- a/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIService.kt +++ b/virtusize/src/main/java/com/virtusize/android/network/VirtusizeAPIService.kt @@ -48,25 +48,24 @@ import javax.net.ssl.HttpsURLConnection */ internal class VirtusizeAPIService( private var context: Context, - private var messageHandler: VirtusizeMessageHandler? + private var messageHandler: VirtusizeMessageHandler?, ) { - companion object { - private var INSTANCE: VirtusizeAPIService? = null + private var instance: VirtusizeAPIService? = null /** * Gets the instance of [VirtusizeAPIService] */ fun getInstance( context: Context, - messageHandler: VirtusizeMessageHandler? + messageHandler: VirtusizeMessageHandler?, ): VirtusizeAPIService { - if (INSTANCE == null) { + if (instance == null) { synchronized(VirtusizeAPIService::javaClass) { - INSTANCE = VirtusizeAPIService(context, messageHandler) + instance = VirtusizeAPIService(context, messageHandler) } } - return INSTANCE!! + return instance!! } } @@ -104,15 +103,13 @@ internal class VirtusizeAPIService( * @param product [VirtusizeProduct] * @return the [VirtusizeApiResponse] with the data class [ProductCheck] */ - internal suspend fun productDataCheck( - product: VirtusizeProduct - ): VirtusizeApiResponse = + internal suspend fun productDataCheck(product: VirtusizeProduct): VirtusizeApiResponse = withContext(Dispatchers.IO) { val apiRequest = VirtusizeApi.productCheck(product) VirtusizeApiTask( httpURLConnection, sharedPreferencesHelper, - messageHandler + messageHandler, ) .setJsonParser(ProductCheckJsonParser()) .execute(apiRequest) @@ -123,15 +120,13 @@ internal class VirtusizeAPIService( * @param product [VirtusizeProduct] * @return the [VirtusizeApiResponse] with the data class [ProductMetaDataHints] */ - internal suspend fun sendProductImageToBackend( - product: VirtusizeProduct - ): VirtusizeApiResponse = + internal suspend fun sendProductImageToBackend(product: VirtusizeProduct): VirtusizeApiResponse = withContext(Dispatchers.IO) { val apiRequest = VirtusizeApi.sendProductImageToBackend(product = product) VirtusizeApiTask( httpURLConnection, sharedPreferencesHelper, - messageHandler + messageHandler, ) .setJsonParser(ProductMetaDataHintsJsonParser()) .execute(apiRequest) @@ -145,30 +140,33 @@ internal class VirtusizeAPIService( */ internal suspend fun sendEvent( event: VirtusizeEvent, - withDataProduct: ProductCheck? = null - ): VirtusizeApiResponse = withContext(Dispatchers.IO) { - val defaultDisplay = - (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay - resolution = "${defaultDisplay.height}x${defaultDisplay.width}" + withDataProduct: ProductCheck? = null, + ): VirtusizeApiResponse = + withContext(Dispatchers.IO) { + val defaultDisplay = + (context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay + resolution = "${defaultDisplay.height}x${defaultDisplay.width}" - val apiRequest = VirtusizeApi.sendEventToAPI( - virtusizeEvent = event, - productCheck = withDataProduct, - deviceOrientation = - if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) - context.getString(R.string.landscape) - else - context.getString(R.string.portrait), - screenResolution = resolution, - versionName = BuildConfig.VERSION_NAME - ) - VirtusizeApiTask( - httpURLConnection, - sharedPreferencesHelper, - messageHandler - ) - .execute(apiRequest) - } + val apiRequest = + VirtusizeApi.sendEventToAPI( + virtusizeEvent = event, + productCheck = withDataProduct, + deviceOrientation = + if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + context.getString(R.string.landscape) + } else { + context.getString(R.string.portrait) + }, + screenResolution = resolution, + versionName = BuildConfig.VERSION_NANE, + ) + VirtusizeApiTask( + httpURLConnection, + sharedPreferencesHelper, + messageHandler, + ) + .execute(apiRequest) + } /** * Sends an order to the Virtusize server @@ -176,33 +174,35 @@ internal class VirtusizeAPIService( */ internal suspend fun sendOrder( region: String?, - order: VirtusizeOrder - ): VirtusizeApiResponse = withContext(Dispatchers.IO) { - // Sets the region from the store info - order.setRegion(region) - val apiRequest = VirtusizeApi.sendOrder(order) - VirtusizeApiTask( - httpURLConnection, - sharedPreferencesHelper, - messageHandler - ) - .execute(apiRequest) - } + order: VirtusizeOrder, + ): VirtusizeApiResponse = + withContext(Dispatchers.IO) { + // Sets the region from the store info + order.setRegion(region) + val apiRequest = VirtusizeApi.sendOrder(order) + VirtusizeApiTask( + httpURLConnection, + sharedPreferencesHelper, + messageHandler, + ) + .execute(apiRequest) + } /** * Gets the API response for retrieving the specific store info * @return the [VirtusizeApiResponse] with the data class [Store] */ - internal suspend fun getStoreInfo(): VirtusizeApiResponse = withContext(Dispatchers.IO) { - val apiRequest = VirtusizeApi.getStoreInfo() - VirtusizeApiTask( - httpURLConnection, - sharedPreferencesHelper, - messageHandler - ) - .setJsonParser(StoreJsonParser()) - .execute(apiRequest) - } + internal suspend fun getStoreInfo(): VirtusizeApiResponse = + withContext(Dispatchers.IO) { + val apiRequest = VirtusizeApi.getStoreInfo() + VirtusizeApiTask( + httpURLConnection, + sharedPreferencesHelper, + messageHandler, + ) + .setJsonParser(StoreJsonParser()) + .execute(apiRequest) + } /** * Gets the API response for retrieving the store product info @@ -211,18 +211,18 @@ internal class VirtusizeAPIService( */ internal suspend fun getStoreProduct(productId: Int): VirtusizeApiResponse = withContext( - Dispatchers.IO + Dispatchers.IO, ) { if (productId == 0) { return@withContext VirtusizeApiResponse.Error( - VirtusizeErrorType.NullProduct.virtusizeError() + VirtusizeErrorType.NullProduct.virtusizeError(), ) } val apiRequest = VirtusizeApi.getStoreProductInfo(productId.toString()) VirtusizeApiTask( httpURLConnection, sharedPreferencesHelper, - messageHandler + messageHandler, ) .setJsonParser(StoreProductJsonParser()) .execute(apiRequest) @@ -232,85 +232,90 @@ internal class VirtusizeAPIService( * Gets the API response for retrieving the list of the product types * @return the [VirtusizeApiResponse] with a list of [ProductType] */ - internal suspend fun getProductTypes(): VirtusizeApiResponse> = withContext( - Dispatchers.IO - ) { - val apiRequest = VirtusizeApi.getProductTypes() - VirtusizeApiTask( - httpURLConnection, - sharedPreferencesHelper, - messageHandler - ) - .setJsonParser(ProductTypeJsonParser()) - .execute(apiRequest) - } + internal suspend fun getProductTypes(): VirtusizeApiResponse> = + withContext( + Dispatchers.IO, + ) { + val apiRequest = VirtusizeApi.getProductTypes() + VirtusizeApiTask( + httpURLConnection, + sharedPreferencesHelper, + messageHandler, + ) + .setJsonParser(ProductTypeJsonParser()) + .execute(apiRequest) + } /** * Gets the API response for getting the user session data * @return the [VirtusizeApiResponse] with [UserSessionInfo] */ - internal suspend fun getUserSessionInfo(): VirtusizeApiResponse = withContext( - Dispatchers.IO - ) { - val apiRequest = VirtusizeApi.getSessions() - VirtusizeApiTask( - httpURLConnection, - sharedPreferencesHelper, - messageHandler - ) - .setJsonParser(UserSessionInfoJsonParser()) - .execute(apiRequest) - } + internal suspend fun getUserSessionInfo(): VirtusizeApiResponse = + withContext( + Dispatchers.IO, + ) { + val apiRequest = VirtusizeApi.getSessions() + VirtusizeApiTask( + httpURLConnection, + sharedPreferencesHelper, + messageHandler, + ) + .setJsonParser(UserSessionInfoJsonParser()) + .execute(apiRequest) + } /** * Gets the API response for deleting a user * @return the [VirtusizeApiResponse] */ - internal suspend fun deleteUser(): VirtusizeApiResponse = withContext( - Dispatchers.IO - ) { - val apiRequest = VirtusizeApi.deleteUser() - VirtusizeApiTask( - httpURLConnection, - sharedPreferencesHelper, - messageHandler - ) - .execute(apiRequest) - } + internal suspend fun deleteUser(): VirtusizeApiResponse = + withContext( + Dispatchers.IO, + ) { + val apiRequest = VirtusizeApi.deleteUser() + VirtusizeApiTask( + httpURLConnection, + sharedPreferencesHelper, + messageHandler, + ) + .execute(apiRequest) + } /** * Gets the API response for retrieving a list of user products * @return the [VirtusizeApiResponse] with the list of [Product] */ - internal suspend fun getUserProducts(): VirtusizeApiResponse> = withContext( - Dispatchers.IO - ) { - val apiRequest = VirtusizeApi.getUserProducts() - VirtusizeApiTask( - httpURLConnection, - sharedPreferencesHelper, - messageHandler - ) - .setJsonParser(UserProductJsonParser()) - .execute(apiRequest) - } + internal suspend fun getUserProducts(): VirtusizeApiResponse> = + withContext( + Dispatchers.IO, + ) { + val apiRequest = VirtusizeApi.getUserProducts() + VirtusizeApiTask( + httpURLConnection, + sharedPreferencesHelper, + messageHandler, + ) + .setJsonParser(UserProductJsonParser()) + .execute(apiRequest) + } /** * Gets the API response for retrieving the current user body profile such as age, height, weight and body measurements * @return the [VirtusizeApiResponse] with the data class [UserBodyProfile] */ - internal suspend fun getUserBodyProfile(): VirtusizeApiResponse = withContext( - Dispatchers.IO - ) { - val apiRequest = VirtusizeApi.getUserBodyProfile() - VirtusizeApiTask( - httpURLConnection, - sharedPreferencesHelper, - messageHandler - ) - .setJsonParser(UserBodyProfileJsonParser()) - .execute(apiRequest) - } + internal suspend fun getUserBodyProfile(): VirtusizeApiResponse = + withContext( + Dispatchers.IO, + ) { + val apiRequest = VirtusizeApi.getUserBodyProfile() + VirtusizeApiTask( + httpURLConnection, + sharedPreferencesHelper, + messageHandler, + ) + .setJsonParser(UserBodyProfileJsonParser()) + .execute(apiRequest) + } /** * Gets the API response for retrieving the recommended size based on the user body profile @@ -322,14 +327,14 @@ internal class VirtusizeAPIService( internal suspend fun getBodyProfileRecommendedSize( productTypes: List, storeProduct: Product, - userBodyProfile: UserBodyProfile + userBodyProfile: UserBodyProfile, ): VirtusizeApiResponse?> = withContext(Dispatchers.IO) { val apiRequest = VirtusizeApi.getSize(productTypes, storeProduct, userBodyProfile) VirtusizeApiTask( httpURLConnection, sharedPreferencesHelper, - messageHandler + messageHandler, ) .setJsonParser(BodyProfileRecommendedSizeJsonParser(storeProduct)) .execute(apiRequest) @@ -340,37 +345,39 @@ internal class VirtusizeAPIService( * @param params [VirtusizeParams] to get the language that is set by a client * @return the [VirtusizeApiResponse] with the data class [I18nLocalization] */ - internal suspend fun getI18n(language: VirtusizeLanguage?): - VirtusizeApiResponse = withContext(Dispatchers.IO) { - val apiRequest = VirtusizeApi.getI18n( - language ?: ( - VirtusizeLanguage.values() - .find { it.value == Locale.getDefault().language } ?: VirtusizeLanguage.EN + internal suspend fun getI18n(language: VirtusizeLanguage?): VirtusizeApiResponse = + withContext(Dispatchers.IO) { + val apiRequest = + VirtusizeApi.getI18n( + language ?: ( + VirtusizeLanguage.values() + .find { it.value == Locale.getDefault().language } ?: VirtusizeLanguage.EN + ), ) - ) - VirtusizeApiTask( - httpURLConnection, - sharedPreferencesHelper, - messageHandler - ) - .setJsonParser(I18nLocalizationJsonParser(context, language)) - .execute(apiRequest) - } + VirtusizeApiTask( + httpURLConnection, + sharedPreferencesHelper, + messageHandler, + ) + .setJsonParser(I18nLocalizationJsonParser(context, language)) + .execute(apiRequest) + } /** * Loads an image URL and returns the bitmap of the image * @param urlString the image URL string * @return the Bitmap of the image */ - internal suspend fun loadImage(urlString: String): Bitmap? = withContext( - Dispatchers.IO - ) { - try { - URL(urlString).openStream().use { - return@withContext BitmapFactory.decodeStream(it) + internal suspend fun loadImage(urlString: String): Bitmap? = + withContext( + Dispatchers.IO, + ) { + try { + URL(urlString).openStream().use { + return@withContext BitmapFactory.decodeStream(it) + } + } catch (e: Exception) { + return@withContext null } - } catch (e: Exception) { - return@withContext null } - } } diff --git a/virtusize/src/main/java/com/virtusize/android/ui/DotsLoadingTextView.kt b/virtusize/src/main/java/com/virtusize/android/ui/DotsLoadingTextView.kt index ae2a3704..d7a48f21 100644 --- a/virtusize/src/main/java/com/virtusize/android/ui/DotsLoadingTextView.kt +++ b/virtusize/src/main/java/com/virtusize/android/ui/DotsLoadingTextView.kt @@ -10,7 +10,6 @@ import androidx.appcompat.widget.AppCompatTextView * A custom TextView to display animated dots in a text */ internal class DotsLoadingTextView : AppCompatTextView { - private var originalText: CharSequence? = null private var dotsLoadingRunnable: Runnable? = null private var dotsLoadingHandler: Handler? = null @@ -22,7 +21,7 @@ internal class DotsLoadingTextView : AppCompatTextView { constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( context, attrs, - defStyleAttr + defStyleAttr, ) /** @@ -31,18 +30,19 @@ internal class DotsLoadingTextView : AppCompatTextView { fun startAnimation() { originalText = text dotsLoadingHandler = Handler(Looper.getMainLooper()) - dotsLoadingRunnable = object : Runnable { - override fun run() { - dotsLoadingHandler?.postDelayed(this, 500) - if (tempDots == maxDots) { - tempDots = 0 - text = originalText - } else { - text = "${originalText}${getDot(++tempDots)}" + dotsLoadingRunnable = + object : Runnable { + override fun run() { + dotsLoadingHandler?.postDelayed(this, 500) + if (tempDots == maxDots) { + tempDots = 0 + text = originalText + } else { + text = "${originalText}${getDot(++tempDots)}" + } + invalidate() } - invalidate() } - } dotsLoadingRunnable!!.run() } diff --git a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeButton.kt b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeButton.kt index 2babfe34..97db5f66 100644 --- a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeButton.kt +++ b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeButton.kt @@ -14,127 +14,136 @@ import com.virtusize.android.data.local.VirtusizeProduct import com.virtusize.android.data.local.VirtusizeViewStyle import com.virtusize.android.util.VirtusizeUtils -class VirtusizeButton @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : VirtusizeView, AppCompatButton(context, attrs, defStyleAttr) { - - /** - * @see VirtusizeView.clientProduct - */ - override var clientProduct: VirtusizeProduct? = null - - /** - * @see VirtusizeView.virtusizeParams - */ - override lateinit var virtusizeParams: VirtusizeParams - - /** - * @see VirtusizeView.virtusizeMessageHandler - */ - override lateinit var virtusizeMessageHandler: VirtusizeMessageHandler - - /** - * @see VirtusizeView.virtusizeDialogFragment - */ - override lateinit var virtusizeDialogFragment: VirtusizeWebViewFragment +class VirtusizeButton + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : VirtusizeView, AppCompatButton(context, attrs, defStyleAttr) { + /** + * @see VirtusizeView.clientProduct + */ + override var clientProduct: VirtusizeProduct? = null + + /** + * @see VirtusizeView.virtusizeParams + */ + override lateinit var virtusizeParams: VirtusizeParams + + /** + * @see VirtusizeView.virtusizeMessageHandler + */ + override lateinit var virtusizeMessageHandler: VirtusizeMessageHandler + + /** + * @see VirtusizeView.virtusizeDialogFragment + */ + override lateinit var virtusizeDialogFragment: VirtusizeWebViewFragment + + // The VirtusizeViewStyle that clients can choose to use for this Button + var virtusizeViewStyle: VirtusizeViewStyle = VirtusizeViewStyle.NONE + set(value) { + field = value + setupButtonStyle() + } - // The VirtusizeViewStyle that clients can choose to use for this Button - var virtusizeViewStyle: VirtusizeViewStyle = VirtusizeViewStyle.NONE - set(value) { - field = value + init { + visibility = + if (visibility == View.GONE) { + View.GONE + } else { + View.INVISIBLE + } + val attrsArray = + context.obtainStyledAttributes( + attrs, + R.styleable.VirtusizeButton, + 0, + 0, + ) + val buttonStyle = + attrsArray.getInt( + R.styleable.VirtusizeButton_virtusizeButtonStyle, + VirtusizeViewStyle.NONE.value, + ) + virtusizeViewStyle = VirtusizeViewStyle.values().firstOrNull { it.value == buttonStyle } + ?: VirtusizeViewStyle.NONE + attrsArray.recycle() setupButtonStyle() } - init { - visibility = if (visibility == View.GONE) { - View.GONE - } else { - View.INVISIBLE - } - val attrsArray = context.obtainStyledAttributes(attrs, R.styleable.VirtusizeButton, 0, 0) - val buttonStyle = attrsArray.getInt( - R.styleable.VirtusizeButton_virtusizeButtonStyle, - VirtusizeViewStyle.NONE.value - ) - virtusizeViewStyle = VirtusizeViewStyle.values().firstOrNull { it.value == buttonStyle } - ?: VirtusizeViewStyle.NONE - attrsArray.recycle() - setupButtonStyle() - } - - /** - * Sets up the Virtusize Button Style corresponding to [VirtusizeViewStyle] - */ - private fun setupButtonStyle() { - if (virtusizeViewStyle == VirtusizeViewStyle.NONE) { - return - } + /** + * Sets up the Virtusize Button Style corresponding to [VirtusizeViewStyle] + */ + private fun setupButtonStyle() { + if (virtusizeViewStyle == VirtusizeViewStyle.NONE) { + return + } - includeFontPadding = false - isAllCaps = false - minHeight = 0 - minWidth = 0 - minimumWidth = 0 - minimumHeight = resources.getDimension(R.dimen.virtusize_button_corner_radius).toInt() + includeFontPadding = false + isAllCaps = false + minHeight = 0 + minWidth = 0 + minimumWidth = 0 + minimumHeight = resources.getDimension(R.dimen.virtusize_button_corner_radius).toInt() + + if (virtusizeViewStyle == VirtusizeViewStyle.TEAL) { + setBackgroundResource(R.drawable.button_background_teal) + } else { + setBackgroundResource(R.drawable.button_background_black) + } - if (virtusizeViewStyle == VirtusizeViewStyle.TEAL) { - setBackgroundResource(R.drawable.button_background_teal) - } else { - setBackgroundResource(R.drawable.button_background_black) - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + setTextAppearance(R.style.VirtusizeButtonTextAppearance) + } else { + setTextAppearance(context, R.style.VirtusizeButtonTextAppearance) + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - setTextAppearance(R.style.VirtusizeButtonTextAppearance) - } else { - setTextAppearance(context, R.style.VirtusizeButtonTextAppearance) + val drawable = ContextCompat.getDrawable(context, R.drawable.ic_vs_icon_white) + val drawableWidth = + resources.getDimension(R.dimen.virtusize_button_logo_default_width).toInt() + val drawableHeight = + resources.getDimension(R.dimen.virtusize_button_logo_default_height).toInt() + drawable?.setBounds(0, 0, drawableWidth, drawableHeight) + setCompoundDrawables(drawable, null, null, null) + compoundDrawablePadding = + resources.getDimension(R.dimen.virtusize_button_text_marginStart).toInt() + + val horizontalPadding = + resources.getDimension(R.dimen.virtusize_button_horizontal_padding).toInt() + val verticalPadding = + resources.getDimension(R.dimen.virtusize_button_vertical_padding).toInt() + setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding) } - val drawable = ContextCompat.getDrawable(context, R.drawable.ic_vs_icon_white) - val drawableWidth = - resources.getDimension(R.dimen.virtusize_button_logo_default_width).toInt() - val drawableHeight = - resources.getDimension(R.dimen.virtusize_button_logo_default_height).toInt() - drawable?.setBounds(0, 0, drawableWidth, drawableHeight) - setCompoundDrawables(drawable, null, null, null) - compoundDrawablePadding = - resources.getDimension(R.dimen.virtusize_button_text_marginStart).toInt() - - val horizontalPadding = - resources.getDimension(R.dimen.virtusize_button_horizontal_padding).toInt() - val verticalPadding = - resources.getDimension(R.dimen.virtusize_button_vertical_padding).toInt() - setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding) - } - - /** - * @see VirtusizeView.setProductWithProductDataCheck - * @throws VirtusizeErrorType.NullProduct error - */ - override fun setProductWithProductDataCheck(productWithPDC: VirtusizeProduct) { - super.setProductWithProductDataCheck(productWithPDC) - if (clientProduct!!.externalId == productWithPDC.externalId) { - clientProduct!!.productCheckData = productWithPDC.productCheckData - visibility = View.VISIBLE - setupButtonTextConfiguredLocalization() - setOnClickListener { - openVirtusizeWebView(context, clientProduct!!) + /** + * @see VirtusizeView.setProductWithProductDataCheck + * @throws VirtusizeErrorType.NullProduct error + */ + override fun setProductWithProductDataCheck(productWithPDC: VirtusizeProduct) { + super.setProductWithProductDataCheck(productWithPDC) + if (clientProduct!!.externalId == productWithPDC.externalId) { + clientProduct!!.productCheckData = productWithPDC.productCheckData + visibility = View.VISIBLE + setupButtonTextConfiguredLocalization() + setOnClickListener { + openVirtusizeWebView(context, clientProduct!!) + } } } - } - /** - * Sets up the button text style based on the language that clients set using the [VirtusizeBuilder] in the application - */ - private fun setupButtonTextConfiguredLocalization() { - val configuredContext = - VirtusizeUtils.getConfiguredContext(context, virtusizeParams.language) - if (text.isNullOrEmpty()) { - text = configuredContext?.getText(R.string.virtusize_button_text) - configuredContext?.resources?.getDimension(R.dimen.virtusize_button_textSize)?.let { - setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + /** + * Sets up the button text style based on the language that clients set using the [VirtusizeBuilder] in the application + */ + private fun setupButtonTextConfiguredLocalization() { + val configuredContext = + VirtusizeUtils.getConfiguredContext(context, virtusizeParams.language) + if (text.isNullOrEmpty()) { + text = configuredContext?.getText(R.string.virtusize_button_text) + configuredContext?.resources?.getDimension(R.dimen.virtusize_button_textSize)?.let { + setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + } } } } -} diff --git a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeFitIllustratorFragment.kt b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeFitIllustratorFragment.kt index 07913da9..f5cf1e6b 100644 --- a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeFitIllustratorFragment.kt +++ b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeFitIllustratorFragment.kt @@ -21,11 +21,13 @@ import com.virtusize.android.util.Constants import com.virtusize.android.util.getActivity class VirtusizeFitIllustratorFragment : DialogFragment() { - companion object { private val TAG = VirtusizeFitIllustratorFragment::class.simpleName - fun launch(activity: Activity?, url: String) { + fun launch( + activity: Activity?, + url: String, + ) { var fragmentManager: FragmentManager? = null if (activity is AppCompatActivity) { fragmentManager = activity.supportFragmentManager @@ -49,7 +51,10 @@ class VirtusizeFitIllustratorFragment : DialogFragment() { } } - fun launch(context: Context, url: String) { + fun launch( + context: Context, + url: String, + ) { launch(context.getActivity(), url) } } @@ -66,33 +71,40 @@ class VirtusizeFitIllustratorFragment : DialogFragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View { binding = WebviewFitIllustratorBinding.inflate(inflater, container, false) return binding.root } @SuppressLint("JavascriptInterface") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) binding.fitIllustratorWebView.enableWindowsSettings() // Set up the web view client that adds a JavaScript script for the click listener to close the button - binding.fitIllustratorWebView.webViewClient = object : WebViewClient() { - override fun onPageFinished(view: WebView?, url: String?) { - view?.loadUrl( - "javascript:(function() { " + - "var element = document.getElementsByClassName('global-close')[0];" + - "element.onclick = function() { " + - "${Constants.JS_BRIDGE_NAME}.userClosedWidget();" + - " };" + - "})()" - ) + binding.fitIllustratorWebView.webViewClient = + object : WebViewClient() { + override fun onPageFinished( + view: WebView?, + url: String?, + ) { + view?.loadUrl( + "javascript:(function() { " + + "var element = document.getElementsByClassName('global-close')[0];" + + "element.onclick = function() { " + + "${Constants.JS_BRIDGE_NAME}.userClosedWidget();" + + " };" + + "})()", + ) + } } - } // Add the Javascript interface to receive events from the web view binding.fitIllustratorWebView.addJavascriptInterface( JavaScriptInterface(), - Constants.JS_BRIDGE_NAME + Constants.JS_BRIDGE_NAME, ) // Get the Fit Illustrator URL passed in fragment arguments arguments?.getString(Constants.URL_KEY)?.let { @@ -105,7 +117,6 @@ class VirtusizeFitIllustratorFragment : DialogFragment() { * The Javascript interface to receive events from the web view */ private inner class JavaScriptInterface { - /** * This method is called when a user clicks on the close button in the Fit Illustrator window */ diff --git a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageMini.kt b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageMini.kt index ca78cbf7..b78e967a 100644 --- a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageMini.kt +++ b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageMini.kt @@ -21,254 +21,283 @@ import com.virtusize.android.util.VirtusizeUtils import com.virtusize.android.util.rightDrawable import com.virtusize.android.util.spToPx -class VirtusizeInPageMini @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : VirtusizeInPageView(context, attrs, defStyleAttr) { +class VirtusizeInPageMini + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : VirtusizeInPageView(context, attrs, defStyleAttr) { + /** + * @see VirtusizeView.clientProduct + */ + override var clientProduct: VirtusizeProduct? = null - /** - * @see VirtusizeView.clientProduct - */ - override var clientProduct: VirtusizeProduct? = null + /** + * @see VirtusizeView.virtusizeParams + */ + override lateinit var virtusizeParams: VirtusizeParams - /** - * @see VirtusizeView.virtusizeParams - */ - override lateinit var virtusizeParams: VirtusizeParams + /** + * @see VirtusizeView.virtusizeMessageHandler + */ + override lateinit var virtusizeMessageHandler: VirtusizeMessageHandler - /** - * @see VirtusizeView.virtusizeMessageHandler - */ - override lateinit var virtusizeMessageHandler: VirtusizeMessageHandler + /** + * @see VirtusizeView.virtusizeDialogFragment + */ + override lateinit var virtusizeDialogFragment: VirtusizeWebViewFragment - /** - * @see VirtusizeView.virtusizeDialogFragment - */ - override lateinit var virtusizeDialogFragment: VirtusizeWebViewFragment - - // The VirtusizeViewStyle that clients can choose to use for this InPage Mini view - var virtusizeViewStyle: VirtusizeViewStyle = VirtusizeViewStyle.NONE - set(value) { - field = value - setStyle() - } + // The VirtusizeViewStyle that clients can choose to use for this InPage Mini view + var virtusizeViewStyle: VirtusizeViewStyle = VirtusizeViewStyle.NONE + set(value) { + field = value + setStyle() + } - // The background color for this InPage Mini view - var virtusizeBackgroundColor = 0 - private set + // The background color for this InPage Mini view + var virtusizeBackgroundColor = 0 + private set - // The configured context for localization - private var configuredContext: ContextWrapper? = null + // The configured context for localization + private var configuredContext: ContextWrapper? = null - private var binding: ViewInpageMiniBinding = - ViewInpageMiniBinding.inflate(LayoutInflater.from(context), this, true) + private var binding: ViewInpageMiniBinding = + ViewInpageMiniBinding.inflate(LayoutInflater.from(context), this, true) - init { - visibility = if (visibility == View.GONE) { - View.GONE - } else { - View.INVISIBLE + init { + visibility = + if (visibility == View.GONE) { + View.GONE + } else { + View.INVISIBLE + } + val attrsArray = + context.obtainStyledAttributes(attrs, R.styleable.VirtusizeInPageMini, 0, 0) + val buttonStyle = + attrsArray.getInt( + R.styleable.VirtusizeInPageMini_virtusizeInPageMiniStyle, + VirtusizeViewStyle.NONE.value, + ) + virtusizeViewStyle = VirtusizeViewStyle.values().firstOrNull { it.value == buttonStyle } + ?: VirtusizeViewStyle.NONE + virtusizeBackgroundColor = + attrsArray.getColor(R.styleable.VirtusizeInPageMini_inPageMiniBackgroundColor, 0) + messageTextSize = + attrsArray.getDimension( + R.styleable.VirtusizeInPageMini_inPageMiniMessageTextSize, + -1f, + ) + buttonTextSize = + attrsArray.getDimension( + R.styleable.VirtusizeInPageMini_inPageMiniButtonTextSize, + -1f, + ) + attrsArray.recycle() + setStyle() } - val attrsArray = - context.obtainStyledAttributes(attrs, R.styleable.VirtusizeInPageMini, 0, 0) - val buttonStyle = attrsArray.getInt( - R.styleable.VirtusizeInPageMini_virtusizeInPageMiniStyle, - VirtusizeViewStyle.NONE.value - ) - virtusizeViewStyle = VirtusizeViewStyle.values().firstOrNull { it.value == buttonStyle } - ?: VirtusizeViewStyle.NONE - virtusizeBackgroundColor = - attrsArray.getColor(R.styleable.VirtusizeInPageMini_inPageMiniBackgroundColor, 0) - messageTextSize = - attrsArray.getDimension(R.styleable.VirtusizeInPageMini_inPageMiniMessageTextSize, -1f) - buttonTextSize = - attrsArray.getDimension(R.styleable.VirtusizeInPageMini_inPageMiniButtonTextSize, -1f) - attrsArray.recycle() - setStyle() - } - /** - * @see VirtusizeView.setProductWithProductDataCheck - * @throws VirtusizeErrorType.NullProduct error - */ - override fun setProductWithProductDataCheck(productWithPDC: VirtusizeProduct) { - super.setProductWithProductDataCheck(productWithPDC) - if (clientProduct!!.externalId == productWithPDC.externalId) { - clientProduct!!.productCheckData = productWithPDC.productCheckData - visibility = View.VISIBLE - setupConfiguredLocalization() - setLoadingScreen(true) - setOnClickListener { - openVirtusizeWebView(context, clientProduct!!) - } - binding.inpageMiniButton.setOnClickListener { - openVirtusizeWebView(context, clientProduct!!) + /** + * @see VirtusizeView.setProductWithProductDataCheck + * @throws VirtusizeErrorType.NullProduct error + */ + override fun setProductWithProductDataCheck(productWithPDC: VirtusizeProduct) { + super.setProductWithProductDataCheck(productWithPDC) + if (clientProduct!!.externalId == productWithPDC.externalId) { + clientProduct!!.productCheckData = productWithPDC.productCheckData + visibility = View.VISIBLE + setupConfiguredLocalization() + setLoadingScreen(true) + setOnClickListener { + openVirtusizeWebView(context, clientProduct!!) + } + binding.inpageMiniButton.setOnClickListener { + openVirtusizeWebView(context, clientProduct!!) + } } } - } - /** - * @see VirtusizeInPageView.setRecommendationText - */ - override fun setRecommendationText(externalProductId: String, text: String) { - if (clientProduct!!.externalId != externalProductId) { - return + /** + * @see VirtusizeInPageView.setRecommendationText + */ + override fun setRecommendationText( + externalProductId: String, + text: String, + ) { + if (clientProduct!!.externalId != externalProductId) { + return + } + binding.inpageMiniText.text = text + setLoadingScreen(false) } - binding.inpageMiniText.text = text - setLoadingScreen(false) - } - /** - * @see VirtusizeInPageView.showInPageError - */ - override fun showInPageError(externalProductId: String?) { - if (clientProduct!!.externalId != externalProductId) { - return - } - binding.inpageMiniLoadingText.visibility = View.GONE - binding.inpageMiniText.visibility = View.VISIBLE - binding.inpageMiniText.text = configuredContext?.getText(R.string.inpage_short_error_text) - binding.inpageMiniText.setTextColor(ContextCompat.getColor(context, R.color.color_gray_700)) - binding.inpageMiniImageView.setImageDrawable( - ContextCompat.getDrawable( - context, - R.drawable.ic_error_hanger + /** + * @see VirtusizeInPageView.showInPageError + */ + override fun showInPageError(externalProductId: String?) { + if (clientProduct!!.externalId != externalProductId) { + return + } + binding.inpageMiniLoadingText.visibility = View.GONE + binding.inpageMiniText.visibility = View.VISIBLE + binding.inpageMiniText.text = + configuredContext?.getText( + R.string.inpage_short_error_text, + ) + binding.inpageMiniText.setTextColor( + ContextCompat.getColor(context, R.color.color_gray_700), ) - ) - setOnClickListener {} - } - - /** - * Sets up the background color of the InPage Mini - * @param color a color int - */ - fun setInPageMiniBackgroundColor(@ColorInt color: Int) { - virtusizeBackgroundColor = color - setStyle() - } - - /** - * Sets up the styles for the loading screen and the screen after finishing loading - * @param loading pass true when it's loading, and pass false when finishing loading - */ - private fun setLoadingScreen(loading: Boolean) { - if (loading) { - binding.inpageMiniLayout.setBackgroundColor( - ContextCompat.getColor( + binding.inpageMiniImageView.setImageDrawable( + ContextCompat.getDrawable( context, - R.color.virtusizeWhite - ) + R.drawable.ic_error_hanger, + ), ) - binding.inpageMiniLoadingText.startAnimation() - } else { - binding.inpageMiniLayout.setBackgroundColor(virtusizeBackgroundColor) - binding.inpageMiniLoadingText.stopAnimation() + setOnClickListener {} } - FontUtils.setTypeFace( - context, - binding.inpageMiniLoadingText, - virtusizeParams.language, - if (loading) FontUtils.FontType.BOLD else FontUtils.FontType.REGULAR - ) - binding.inpageMiniImageView.visibility = if (loading) View.VISIBLE else View.GONE - binding.inpageMiniText.visibility = if (loading) View.GONE else View.VISIBLE - binding.inpageMiniLoadingText.visibility = if (loading) View.VISIBLE else View.GONE - binding.inpageMiniButton.visibility = if (loading) View.GONE else View.VISIBLE - } - /** - * Sets the InPage Mini style corresponding to [VirtusizeViewStyle] - */ - override fun setStyle() { - if (virtusizeBackgroundColor != 0) { - binding.inpageMiniLayout.setBackgroundColor(virtusizeBackgroundColor) - binding.inpageMiniButton.setTextColor(virtusizeBackgroundColor) - setButtonRightArrowColor(virtusizeBackgroundColor) - } else if (virtusizeViewStyle == VirtusizeViewStyle.TEAL) { - binding.inpageMiniLayout.setBackgroundColor( - ContextCompat.getColor( - context, - R.color.virtusizeTeal + /** + * Sets up the background color of the InPage Mini + * @param color a color int + */ + fun setInPageMiniBackgroundColor( + @ColorInt color: Int, + ) { + virtusizeBackgroundColor = color + setStyle() + } + + /** + * Sets up the styles for the loading screen and the screen after finishing loading + * @param loading pass true when it's loading, and pass false when finishing loading + */ + private fun setLoadingScreen(loading: Boolean) { + if (loading) { + binding.inpageMiniLayout.setBackgroundColor( + ContextCompat.getColor( + context, + R.color.virtusizeWhite, + ), ) + binding.inpageMiniLoadingText.startAnimation() + } else { + binding.inpageMiniLayout.setBackgroundColor(virtusizeBackgroundColor) + binding.inpageMiniLoadingText.stopAnimation() + } + FontUtils.setTypeFace( + context, + binding.inpageMiniLoadingText, + virtusizeParams.language, + if (loading) FontUtils.FontType.BOLD else FontUtils.FontType.REGULAR, ) - binding.inpageMiniButton.setTextColor( - ContextCompat.getColor(context, R.color.virtusizeTeal) - ) - setButtonRightArrowColor(ContextCompat.getColor(context, R.color.virtusizeTeal)) + binding.inpageMiniImageView.visibility = if (loading) View.VISIBLE else View.GONE + binding.inpageMiniText.visibility = if (loading) View.GONE else View.VISIBLE + binding.inpageMiniLoadingText.visibility = if (loading) View.VISIBLE else View.GONE + binding.inpageMiniButton.visibility = if (loading) View.GONE else View.VISIBLE } - } - - /** - * Sets up the color of the right arrow in the button - */ - private fun setButtonRightArrowColor(color: Int) { - var drawable = ContextCompat.getDrawable(context, R.drawable.ic_arrow_right_black) - drawable = DrawableCompat.wrap(drawable!!) - drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) - DrawableCompat.setTint(drawable, color) - binding.inpageMiniButton.setCompoundDrawables(null, null, drawable, null) - } + /** + * Sets the InPage Mini style corresponding to [VirtusizeViewStyle] + */ + override fun setStyle() { + if (virtusizeBackgroundColor != 0) { + binding.inpageMiniLayout.setBackgroundColor(virtusizeBackgroundColor) + binding.inpageMiniButton.setTextColor(virtusizeBackgroundColor) + setButtonRightArrowColor(virtusizeBackgroundColor) + } else if (virtusizeViewStyle == VirtusizeViewStyle.TEAL) { + binding.inpageMiniLayout.setBackgroundColor( + ContextCompat.getColor( + context, + R.color.virtusizeTeal, + ), + ) + binding.inpageMiniButton.setTextColor( + ContextCompat.getColor(context, R.color.virtusizeTeal), + ) + setButtonRightArrowColor(ContextCompat.getColor(context, R.color.virtusizeTeal)) + } + } - /** - * Sets up the text fonts, localization, and UI dimensions based on the configured context - */ - private fun setupConfiguredLocalization() { - FontUtils.setTypeFaces( - context, - mutableListOf( - binding.inpageMiniText, - binding.inpageMiniButton - ), - virtusizeParams.language, FontUtils.FontType.REGULAR - ) - configuredContext = VirtusizeUtils.getConfiguredContext(context, virtusizeParams.language) - binding.inpageMiniButton.text = configuredContext?.getText(R.string.virtusize_button_text) - binding.inpageMiniLoadingText.text = - configuredContext?.getText(R.string.inpage_loading_text) - setConfiguredDimensions() + /** + * Sets up the color of the right arrow in the button + */ + private fun setButtonRightArrowColor(color: Int) { + var drawable = ContextCompat.getDrawable(context, R.drawable.ic_arrow_right_black) + drawable = DrawableCompat.wrap(drawable!!) + drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) + DrawableCompat.setTint(drawable, color) - if (virtusizeParams.language == VirtusizeLanguage.JP) { - binding.inpageMiniText.includeFontPadding = true + binding.inpageMiniButton.setCompoundDrawables(null, null, drawable, null) } - } - /** - * Sets up text sizes based on the configured context - */ - private fun setConfiguredDimensions() { - val additionalSize = if (virtusizeParams.language == VirtusizeLanguage.EN) 2f.spToPx else 0f - if (messageTextSize != -1f) { - binding.inpageMiniLoadingText.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - messageTextSize + additionalSize - ) - binding.inpageMiniText.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - messageTextSize + additionalSize + /** + * Sets up the text fonts, localization, and UI dimensions based on the configured context + */ + private fun setupConfiguredLocalization() { + FontUtils.setTypeFaces( + context, + mutableListOf( + binding.inpageMiniText, + binding.inpageMiniButton, + ), + virtusizeParams.language, + FontUtils.FontType.REGULAR, ) - } else { - configuredContext?.resources?.getDimension( - R.dimen.virtusize_inpage_mini_message_textSize - )?.let { - binding.inpageMiniLoadingText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) - binding.inpageMiniText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + configuredContext = + VirtusizeUtils.getConfiguredContext( + context, + virtusizeParams.language, + ) + binding.inpageMiniButton.text = + configuredContext?.getText( + R.string.virtusize_button_text, + ) + binding.inpageMiniLoadingText.text = + configuredContext?.getText(R.string.inpage_loading_text) + setConfiguredDimensions() + + if (virtusizeParams.language == VirtusizeLanguage.JP) { + binding.inpageMiniText.includeFontPadding = true } } - if (buttonTextSize != -1f) { - val size = buttonTextSize + additionalSize - binding.inpageMiniButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, size) - binding.inpageMiniButton.rightDrawable( - R.drawable.ic_arrow_right_black, - 0.8f * size / 2, - 0.8f * size - ) - } else { - configuredContext?.resources?.getDimension(R.dimen.virtusize_inpage_default_textSize) - ?.let { - binding.inpageMiniButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + + /** + * Sets up text sizes based on the configured context + */ + private fun setConfiguredDimensions() { + val additionalSize = if (virtusizeParams.language == VirtusizeLanguage.EN) 2f.spToPx else 0f + if (messageTextSize != -1f) { + binding.inpageMiniLoadingText.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + messageTextSize + additionalSize, + ) + binding.inpageMiniText.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + messageTextSize + additionalSize, + ) + } else { + configuredContext?.resources?.getDimension( + R.dimen.virtusize_inpage_mini_message_textSize, + )?.let { + binding.inpageMiniLoadingText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + binding.inpageMiniText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) } + } + if (buttonTextSize != -1f) { + val size = buttonTextSize + additionalSize + binding.inpageMiniButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, size) + binding.inpageMiniButton.rightDrawable( + R.drawable.ic_arrow_right_black, + 0.8f * size / 2, + 0.8f * size, + ) + } else { + configuredContext?.resources?.getDimension( + R.dimen.virtusize_inpage_default_textSize, + ) + ?.let { + binding.inpageMiniButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + } + } } } -} diff --git a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageStandard.kt b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageStandard.kt index fdfddfb4..705b95b1 100644 --- a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageStandard.kt +++ b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageStandard.kt @@ -38,610 +38,667 @@ import com.virtusize.android.util.onSizeChanged import com.virtusize.android.util.rightDrawable import com.virtusize.android.util.spToPx -class VirtusizeInPageStandard @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : VirtusizeInPageView(context, attrs, defStyleAttr) { - - /** - * @see VirtusizeView.clientProduct - */ - override var clientProduct: VirtusizeProduct? = null - - /** - * @see VirtusizeView.virtusizeParams - */ - override lateinit var virtusizeParams: VirtusizeParams - - /** - * @see VirtusizeView.virtusizeMessageHandler - */ - override lateinit var virtusizeMessageHandler: VirtusizeMessageHandler - - /** - * @see VirtusizeView.virtusizeDialogFragment - */ - override lateinit var virtusizeDialogFragment: VirtusizeWebViewFragment - - // If the width of the InPage is small than 411dp, the value is true - private var smallInPageWidth = false - - // The duration of how long the cross fade animation for product images should be - private val crossFadeAnimationDuration = 750 - - // The cross fade animation Runnable - private var crossFadeRunnable: Runnable? = null - - // The cross fade animation Handler - private var crossFadeHandler: Handler = Handler(Looper.getMainLooper()) - - private var userBestFitProduct: Product? = null - - // The VirtusizeViewStyle that clients can choose to use for this InPage Standard view - var virtusizeViewStyle = VirtusizeViewStyle.NONE - set(value) { - field = value - setStyle() - } +class VirtusizeInPageStandard + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : VirtusizeInPageView(context, attrs, defStyleAttr) { + /** + * @see VirtusizeView.clientProduct + */ + override var clientProduct: VirtusizeProduct? = null + + /** + * @see VirtusizeView.virtusizeParams + */ + override lateinit var virtusizeParams: VirtusizeParams + + /** + * @see VirtusizeView.virtusizeMessageHandler + */ + override lateinit var virtusizeMessageHandler: VirtusizeMessageHandler + + /** + * @see VirtusizeView.virtusizeDialogFragment + */ + override lateinit var virtusizeDialogFragment: VirtusizeWebViewFragment + + // If the width of the InPage is small than 411dp, the value is true + private var smallInPageWidth = false + + // The duration of how long the cross fade animation for product images should be + private val crossFadeAnimationDuration = 750 + + // The cross fade animation Runnable + private var crossFadeRunnable: Runnable? = null + + // The cross fade animation Handler + private var crossFadeHandler: Handler = Handler(Looper.getMainLooper()) + + private var userBestFitProduct: Product? = null + + // The VirtusizeViewStyle that clients can choose to use for this InPage Standard view + var virtusizeViewStyle = VirtusizeViewStyle.NONE + set(value) { + field = value + setStyle() + } - // The background color that clients can set up for the button - var virtusizeButtonBackgroundColor = 0 - private set + // The background color that clients can set up for the button + var virtusizeButtonBackgroundColor = 0 + private set - // The horizontal margin between the edges of InPage Standard view and the phone screen - var horizontalMargin = -1 - set(value) { - field = value - setStyle() - } + // The horizontal margin between the edges of InPage Standard view and the phone screen + var horizontalMargin = -1 + set(value) { + field = value + setStyle() + } - private val binding = ViewInpageStandardBinding.inflate( - LayoutInflater.from(context), this, true - ) - private var viewModel: VirtusizeInPageStandardViewModel? = null + private val binding = + ViewInpageStandardBinding.inflate( + LayoutInflater.from(context), + this, + true, + ) + private var viewModel: VirtusizeInPageStandardViewModel? = null - init { - visibility = if (visibility == View.GONE) { - View.GONE - } else { - View.INVISIBLE - } + init { + visibility = + if (visibility == View.GONE) { + View.GONE + } else { + View.INVISIBLE + } - val attrsArray = context.obtainStyledAttributes( - attrs, - R.styleable.VirtusizeInPageStandard, - 0, - 0 - ) - val buttonStyle = attrsArray.getInt( - R.styleable.VirtusizeInPageStandard_virtusizeInPageStandardStyle, - VirtusizeViewStyle.NONE.value - ) - virtusizeViewStyle = VirtusizeViewStyle.values().firstOrNull { it.value == buttonStyle } - ?: VirtusizeViewStyle.NONE - virtusizeButtonBackgroundColor = attrsArray.getColor( - R.styleable.VirtusizeInPageStandard_inPageStandardButtonBackgroundColor, - 0 - ) - horizontalMargin = attrsArray.getDimension( - R.styleable.VirtusizeInPageStandard_inPageStandardHorizontalMargin, - -1f - ).toInt() - messageTextSize = attrsArray.getDimension( - R.styleable.VirtusizeInPageStandard_inPageStandardMessageTextSize, - -1f - ) - buttonTextSize = attrsArray.getDimension( - R.styleable.VirtusizeInPageStandard_inPageStandardButtonTextSize, - -1f - ) - - attrsArray.recycle() - - binding.inpageCardView.onSizeChanged { width, height -> - if (width < 411.dpInPx) { - smallInPageWidth = true - } - } + val attrsArray = + context.obtainStyledAttributes( + attrs, + R.styleable.VirtusizeInPageStandard, + 0, + 0, + ) + val buttonStyle = + attrsArray.getInt( + R.styleable.VirtusizeInPageStandard_virtusizeInPageStandardStyle, + VirtusizeViewStyle.NONE.value, + ) + virtusizeViewStyle = VirtusizeViewStyle.values().firstOrNull { it.value == buttonStyle } + ?: VirtusizeViewStyle.NONE + virtusizeButtonBackgroundColor = + attrsArray.getColor( + R.styleable.VirtusizeInPageStandard_inPageStandardButtonBackgroundColor, + 0, + ) + horizontalMargin = + attrsArray.getDimension( + R.styleable.VirtusizeInPageStandard_inPageStandardHorizontalMargin, + -1f, + ).toInt() + messageTextSize = + attrsArray.getDimension( + R.styleable.VirtusizeInPageStandard_inPageStandardMessageTextSize, + -1f, + ) + buttonTextSize = + attrsArray.getDimension( + R.styleable.VirtusizeInPageStandard_inPageStandardButtonTextSize, + -1f, + ) - setStyle() - } + attrsArray.recycle() - /** - * @see VirtusizeView.initialSetup - */ - override fun initialSetup( - product: VirtusizeProduct, - params: VirtusizeParams, - messageHandler: VirtusizeMessageHandler - ) { - super.initialSetup(product, params, messageHandler) - - val viewModelFactory = object : ViewModelProvider.Factory { - override fun create(modelClass: Class): T { - return VirtusizeInPageStandardViewModel( - VirtusizeRepository( - context, - virtusizeMessageHandler - ) - ) as T + binding.inpageCardView.onSizeChanged { width, height -> + if (width < 411.dpInPx) { + smallInPageWidth = true + } } + + setStyle() } - viewModel = viewModelFactory.create(VirtusizeInPageStandardViewModel::class.java) + /** + * @see VirtusizeView.initialSetup + */ + override fun initialSetup( + product: VirtusizeProduct, + params: VirtusizeParams, + messageHandler: VirtusizeMessageHandler, + ) { + super.initialSetup(product, params, messageHandler) + + val viewModelFactory = + object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return VirtusizeInPageStandardViewModel( + VirtusizeRepository( + context, + virtusizeMessageHandler, + ), + ) as T + } + } - (context as? LifecycleOwner)?.apply { - viewModel?.productNetworkImageLiveData?.observe(this, { productImageViewBitMapPair -> - productImageViewBitMapPair.first.setProductImage(productImageViewBitMapPair.second) - }) + viewModel = viewModelFactory.create(VirtusizeInPageStandardViewModel::class.java) - viewModel?.productPlaceholderImageLiveData?.observe(this, { productImageViewDataPair -> - productImageViewDataPair.first.setProductPlaceHolderImage( - productImageViewDataPair.second.productType, - productImageViewDataPair.second.storeProductMeta?.additionalInfo?.style - ) - }) + (context as? LifecycleOwner)?.apply { + viewModel?.productNetworkImageLiveData?.observe(this, { productImageViewBitMapPair -> + productImageViewBitMapPair.first.setProductImage( + productImageViewBitMapPair.second, + ) + }) - viewModel?.finishLoadingProductImages?.observe(this, { finishLoading -> - if (finishLoading) { - setLoadingScreen(false, userBestFitProduct) - } - }) - } - } + viewModel?.productPlaceholderImageLiveData?.observe(this, { productImageViewDataPair -> + productImageViewDataPair.first.setProductPlaceHolderImage( + productImageViewDataPair.second.productType, + productImageViewDataPair.second.storeProductMeta?.additionalInfo?.style, + ) + }) - /** - * @see VirtusizeView.setProductWithProductDataCheck - * @throws VirtusizeErrorType.NullProduct error - */ - override fun setProductWithProductDataCheck(productWithPDC: VirtusizeProduct) { - super.setProductWithProductDataCheck(productWithPDC) - if (clientProduct!!.externalId == productWithPDC.externalId) { - clientProduct!!.productCheckData = productWithPDC.productCheckData - visibility = View.VISIBLE - setupConfiguredLocalization() - setLoadingScreen(true) - binding.inpageCardView.setOnClickListener { - openVirtusizeWebView(context, clientProduct!!) - } - binding.inpageButton.setOnClickListener { - openVirtusizeWebView(context, clientProduct!!) + viewModel?.finishLoadingProductImages?.observe(this, { finishLoading -> + if (finishLoading) { + setLoadingScreen(false, userBestFitProduct) + } + }) } } - } - /** - * Sets up the styles for the loading screen and the screen after finishing loading - * @param loading pass true when it's loading, and pass false when finishing loading - * @param userBestFitProduct pass the user best fit product to determine whether to display the user product image or not - */ - private fun setLoadingScreen(loading: Boolean, userBestFitProduct: Product? = null) { - binding.inpageStoreProductImageView.visibility = - if (loading) View.INVISIBLE else View.VISIBLE - binding.inpageUserProductImageView.visibility = - if (userBestFitProduct == null) View.GONE else View.VISIBLE - binding.vsSignatureImageView.visibility = if (loading) View.INVISIBLE else View.VISIBLE - binding.privacyPolicyText.visibility = if (loading) View.INVISIBLE else View.VISIBLE - binding.inpageVSIconImageView.visibility = if (loading) View.VISIBLE else View.GONE - binding.inpageLoadingText.visibility = if (loading) View.VISIBLE else View.GONE - if (loading) { - binding.inpageLoadingText.startAnimation() - binding.inpageTopText.visibility = View.GONE - binding.inpageBottomText.visibility = View.GONE - } else { - binding.inpageLoadingText.stopAnimation() - if (!binding.inpageTopText.text.isNullOrBlank()) { - binding.inpageTopText.visibility = View.VISIBLE - } - if (!binding.inpageBottomText.text.isNullOrBlank()) { - binding.inpageBottomText.visibility = View.VISIBLE + /** + * @see VirtusizeView.setProductWithProductDataCheck + * @throws VirtusizeErrorType.NullProduct error + */ + override fun setProductWithProductDataCheck(productWithPDC: VirtusizeProduct) { + super.setProductWithProductDataCheck(productWithPDC) + if (clientProduct!!.externalId == productWithPDC.externalId) { + clientProduct!!.productCheckData = productWithPDC.productCheckData + visibility = View.VISIBLE + setupConfiguredLocalization() + setLoadingScreen(true) + binding.inpageCardView.setOnClickListener { + openVirtusizeWebView(context, clientProduct!!) + } + binding.inpageButton.setOnClickListener { + openVirtusizeWebView(context, clientProduct!!) + } } } - // If the InPage width is small and when the loading is finished - if (smallInPageWidth && !loading) { - if (userBestFitProduct != null) { - startCrossFadeProductImageViews() + /** + * Sets up the styles for the loading screen and the screen after finishing loading + * @param loading pass true when it's loading, and pass false when finishing loading + * @param userBestFitProduct pass the user best fit product to determine whether to display the user product image or not + */ + private fun setLoadingScreen( + loading: Boolean, + userBestFitProduct: Product? = null, + ) { + binding.inpageStoreProductImageView.visibility = + if (loading) View.INVISIBLE else View.VISIBLE + binding.inpageUserProductImageView.visibility = + if (userBestFitProduct == null) View.GONE else View.VISIBLE + binding.vsSignatureImageView.visibility = if (loading) View.INVISIBLE else View.VISIBLE + binding.privacyPolicyText.visibility = if (loading) View.INVISIBLE else View.VISIBLE + binding.inpageVSIconImageView.visibility = if (loading) View.VISIBLE else View.GONE + binding.inpageLoadingText.visibility = if (loading) View.VISIBLE else View.GONE + if (loading) { + binding.inpageLoadingText.startAnimation() + binding.inpageTopText.visibility = View.GONE + binding.inpageBottomText.visibility = View.GONE } else { - stopCrossFadeProductImageViews() + binding.inpageLoadingText.stopAnimation() + if (!binding.inpageTopText.text.isNullOrBlank()) { + binding.inpageTopText.visibility = View.VISIBLE + } + if (!binding.inpageBottomText.text.isNullOrBlank()) { + binding.inpageBottomText.visibility = View.VISIBLE + } } - } - } - /** - * @see VirtusizeInPageView.setRecommendationText - */ - override fun setRecommendationText(externalProductId: String, text: String) { - if (clientProduct!!.externalId != externalProductId) { - return - } - val splitTexts = text.split("
") - if (splitTexts.size == 2) { - binding.inpageTopText.text = splitTexts[0] - binding.inpageBottomText.text = splitTexts[1] - } else { - binding.inpageTopText.text = "" - binding.inpageTopText.visibility = View.GONE - binding.inpageBottomText.text = splitTexts[0] + // If the InPage width is small and when the loading is finished + if (smallInPageWidth && !loading) { + if (userBestFitProduct != null) { + startCrossFadeProductImageViews() + } else { + stopCrossFadeProductImageViews() + } + } } - } - /** - * @see VirtusizeInPageView.showInPageError - */ - override fun showInPageError(externalProductId: String?) { - if (clientProduct!!.externalId != externalProductId) { - return + /** + * @see VirtusizeInPageView.setRecommendationText + */ + override fun setRecommendationText( + externalProductId: String, + text: String, + ) { + if (clientProduct!!.externalId != externalProductId) { + return + } + val splitTexts = text.split("
") + if (splitTexts.size == 2) { + binding.inpageTopText.text = splitTexts[0] + binding.inpageBottomText.text = splitTexts[1] + } else { + binding.inpageTopText.text = "" + binding.inpageTopText.visibility = View.GONE + binding.inpageBottomText.text = splitTexts[0] + } } - binding.inpageErrorScreenLayout.visibility = View.VISIBLE - binding.inpageLayout.visibility = View.GONE - binding.inpageCardView.cardElevation = 0f - binding.inpageCardView.setOnClickListener {} - binding.inpageButton.setOnClickListener {} - } - /** - * Sets up the background color of the button - * @param color a color int - */ - fun setButtonBackgroundColor(@ColorInt color: Int) { - virtusizeButtonBackgroundColor = color - setStyle() - } - - /** - * Sets the product images with the info of the store product and the best fit user product - * @param storeProduct the store product - * @param userBestFitProduct the best fit user product. If it's not available, it will be null - */ - internal fun setProductImages(storeProduct: Product, userBestFitProduct: Product?) { - if (clientProduct!!.externalId != storeProduct.externalId) { - return - } - this.userBestFitProduct = userBestFitProduct - val productMap = mutableMapOf() - productMap[binding.inpageStoreProductImageView] = storeProduct - if (userBestFitProduct != null) { - productMap[binding.inpageUserProductImageView] = userBestFitProduct - removeLeftPaddingFromStoreProductImageView() - } else { - addLeftPaddingToStoreProductImageView(true) + /** + * @see VirtusizeInPageView.showInPageError + */ + override fun showInPageError(externalProductId: String?) { + if (clientProduct!!.externalId != externalProductId) { + return + } + binding.inpageErrorScreenLayout.visibility = View.VISIBLE + binding.inpageLayout.visibility = View.GONE + binding.inpageCardView.cardElevation = 0f + binding.inpageCardView.setOnClickListener {} + binding.inpageButton.setOnClickListener {} } - viewModel?.loadProductImages(productMap) - } - /** - * Adds the left padding to the store product image view - */ - private fun addLeftPaddingToStoreProductImageView(addExtraPadding: Boolean) { - val productImageOverlapMargin = - resources.getDimension(R.dimen.virtusize_inpage_standard_product_image_overlap_margin) - val productImageHorizontalMargin = - resources.getDimension( - R.dimen.virtusize_inpage_standard_product_image_horizontal_margin - ) - var addedPadding = productImageHorizontalMargin.toInt() - if (addExtraPadding) { - addedPadding -= productImageOverlapMargin.toInt() + /** + * Sets up the background color of the button + * @param color a color int + */ + fun setButtonBackgroundColor( + @ColorInt color: Int, + ) { + virtusizeButtonBackgroundColor = color + setStyle() } - binding.inpageStoreProductImageView.setPadding(addedPadding, 0, 0, 0) - } - private fun removeLeftPaddingFromStoreProductImageView() { - binding.inpageStoreProductImageView.setPadding(0, 0, 0, 0) - } - - /** - * Sets the InPage Standard style corresponding to [VirtusizeViewStyle] and [horizontalMargin] - */ - override fun setStyle() { - // Set Virtusize default style - when { - virtusizeButtonBackgroundColor != 0 -> { - setSizeCheckButtonBackgroundTint(virtusizeButtonBackgroundColor) + /** + * Sets the product images with the info of the store product and the best fit user product + * @param storeProduct the store product + * @param userBestFitProduct the best fit user product. If it's not available, it will be null + */ + internal fun setProductImages( + storeProduct: Product, + userBestFitProduct: Product?, + ) { + if (clientProduct!!.externalId != storeProduct.externalId) { + return } - virtusizeViewStyle == VirtusizeViewStyle.TEAL -> { - setSizeCheckButtonBackgroundTint( - ContextCompat.getColor( - context, - R.color.virtusizeTeal - ) - ) + this.userBestFitProduct = userBestFitProduct + val productMap = mutableMapOf() + productMap[binding.inpageStoreProductImageView] = storeProduct + if (userBestFitProduct != null) { + productMap[binding.inpageUserProductImageView] = userBestFitProduct + removeLeftPaddingFromStoreProductImageView() + } else { + addLeftPaddingToStoreProductImageView(true) } - else -> { - setSizeCheckButtonBackgroundTint( - ContextCompat.getColor( - context, - R.color.color_gray_900 - ) + viewModel?.loadProductImages(productMap) + } + + /** + * Adds the left padding to the store product image view + */ + private fun addLeftPaddingToStoreProductImageView(addExtraPadding: Boolean) { + val productImageOverlapMargin = + resources.getDimension( + R.dimen.virtusize_inpage_standard_product_image_overlap_margin, + ) + val productImageHorizontalMargin = + resources.getDimension( + R.dimen.virtusize_inpage_standard_product_image_horizontal_margin, ) + var addedPadding = productImageHorizontalMargin.toInt() + if (addExtraPadding) { + addedPadding -= productImageOverlapMargin.toInt() } + binding.inpageStoreProductImageView.setPadding(addedPadding, 0, 0, 0) } - // Set the background color of the inpage card view - binding.inpageCardView.setBackgroundColor( - ContextCompat.getColor( - context, - R.color.virtusizeWhite - ) - ) - - // Set horizontal margins - val inPageStandardFooterTopMargin = - if (horizontalMargin >= 2.dpInPx) 10.dpInPx - horizontalMargin - else horizontalMargin + 8.dpInPx - if (horizontalMargin < 0) { - return + private fun removeLeftPaddingFromStoreProductImageView() { + binding.inpageStoreProductImageView.setPadding(0, 0, 0, 0) } - setupMargins( - binding.inpageCardView, - horizontalMargin, - horizontalMargin, - horizontalMargin, - horizontalMargin - ) - setupInPageStandardFooterMargins( - horizontalMargin + 2.dpInPx, - inPageStandardFooterTopMargin, - horizontalMargin + 2.dpInPx, - 0 - ) - } - /** - * Starts the cross fade animation to display the user and store product images in an alternating way - */ - private fun startCrossFadeProductImageViews() { - // Remove the settings for layout_toEndOf and layout_toRightOf - val params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) - binding.inpageStoreProductImageView.layoutParams = params - // Add left padding to the store image to math the position of the user image - addLeftPaddingToStoreProductImageView(false) - // Remove any margins to the user product image - setupMargins(binding.inpageUserProductImageView, 8.dpInPx, 0, 0, 0) - if (crossFadeRunnable == null) { - crossFadeRunnable = Runnable { - if (binding.inpageUserProductImageView.visibility == View.VISIBLE) { - // Make sure the store product image is invisible when the animation starts - binding.inpageStoreProductImageView.visibility = View.INVISIBLE - fadeInAnimation( - binding.inpageStoreProductImageView, - binding.inpageUserProductImageView + /** + * Sets the InPage Standard style corresponding to [VirtusizeViewStyle] and [horizontalMargin] + */ + override fun setStyle() { + // Set Virtusize default style + when { + virtusizeButtonBackgroundColor != 0 -> { + setSizeCheckButtonBackgroundTint(virtusizeButtonBackgroundColor) + } + virtusizeViewStyle == VirtusizeViewStyle.TEAL -> { + setSizeCheckButtonBackgroundTint( + ContextCompat.getColor( + context, + R.color.virtusizeTeal, + ), ) - fadeOutAnimation(binding.inpageUserProductImageView) - } else { - fadeInAnimation( - binding.inpageUserProductImageView, - binding.inpageStoreProductImageView + } + else -> { + setSizeCheckButtonBackgroundTint( + ContextCompat.getColor( + context, + R.color.color_gray_900, + ), ) - fadeOutAnimation(binding.inpageStoreProductImageView) } } - crossFadeHandler.postDelayed(crossFadeRunnable!!, 2500) - } - } - /** - * Stops the cross fade animation - */ - private fun stopCrossFadeProductImageViews() { - // Remove the runnable from the handler - crossFadeRunnable?.let { crossFadeHandler.removeCallbacks(it) } - crossFadeRunnable = null - // Make sure the alpha values for product images are back to 1f if they got changed during the animation.= - binding.inpageUserProductImageView.alpha = 1f - binding.inpageStoreProductImageView.alpha = 1f - } + // Set the background color of the inpage card view + binding.inpageCardView.setBackgroundColor( + ContextCompat.getColor( + context, + R.color.virtusizeWhite, + ), + ) - /** - * Fades in an image - * @param imageViewOne the image to be faded in - * @param imageViewTwo the image to be set invisible after the animation is done - */ - private fun fadeInAnimation( - imageViewOne: VirtusizeProductImageView, - imageViewTwo: VirtusizeProductImageView - ) { - imageViewOne.apply { - alpha = 0f - visibility = VISIBLE - - animate() - .alpha(1f) - .setDuration(crossFadeAnimationDuration.toLong()) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - imageViewTwo.visibility = INVISIBLE - } - }) + // Set horizontal margins + val inPageStandardFooterTopMargin = + if (horizontalMargin >= 2.dpInPx) { + 10.dpInPx - horizontalMargin + } else { + horizontalMargin + 8.dpInPx + } + if (horizontalMargin < 0) { + return + } + setupMargins( + binding.inpageCardView, + horizontalMargin, + horizontalMargin, + horizontalMargin, + horizontalMargin, + ) + setupInPageStandardFooterMargins( + horizontalMargin + 2.dpInPx, + inPageStandardFooterTopMargin, + horizontalMargin + 2.dpInPx, + 0, + ) } - } - /** - * Fades out an image - * @param imageView the image to be faded out - */ - private fun fadeOutAnimation(imageView: VirtusizeProductImageView) { - imageView.apply { - animate() - .alpha(0f) - .setDuration(crossFadeAnimationDuration.toLong()) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator) { - crossFadeRunnable?.let { crossFadeHandler.postDelayed(it, 2500) } + /** + * Starts the cross fade animation to display the user and store product images in an alternating way + */ + private fun startCrossFadeProductImageViews() { + // Remove the settings for layout_toEndOf and layout_toRightOf + val params = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) + binding.inpageStoreProductImageView.layoutParams = params + // Add left padding to the store image to math the position of the user image + addLeftPaddingToStoreProductImageView(false) + // Remove any margins to the user product image + setupMargins(binding.inpageUserProductImageView, 8.dpInPx, 0, 0, 0) + if (crossFadeRunnable == null) { + crossFadeRunnable = + Runnable { + if (binding.inpageUserProductImageView.visibility == View.VISIBLE) { + // Make sure the store product image is invisible when the animation starts + binding.inpageStoreProductImageView.visibility = View.INVISIBLE + fadeInAnimation( + binding.inpageStoreProductImageView, + binding.inpageUserProductImageView, + ) + fadeOutAnimation(binding.inpageUserProductImageView) + } else { + fadeInAnimation( + binding.inpageUserProductImageView, + binding.inpageStoreProductImageView, + ) + fadeOutAnimation(binding.inpageStoreProductImageView) + } } - }) + crossFadeHandler.postDelayed(crossFadeRunnable!!, 2500) + } } - } - - /** - * Sets the background color of the size check button - */ - private fun setSizeCheckButtonBackgroundTint(color: Int) { - var drawable = ContextCompat.getDrawable(context, R.drawable.button_background_white) - drawable = DrawableCompat.wrap(drawable!!) - DrawableCompat.setTint(drawable, color) - ViewCompat.setBackground( - binding.inpageButton, - drawable - ) - } - /** - * Sets up the text fonts, localization, and UI dimensions based on the configured context - */ - private fun setupConfiguredLocalization() { - FontUtils.setTypeFaces( - context, - mutableListOf( - binding.inpageTopText, - binding.inpageButton, - binding.privacyPolicyText - ), - virtusizeParams.language, - FontUtils.FontType.REGULAR - ) - FontUtils.setTypeFaces( - context, - mutableListOf( - binding.inpageLoadingText, - binding.inpageBottomText - ), - virtusizeParams.language, - FontUtils.FontType.BOLD - ) - - val configuredContext = VirtusizeUtils.getConfiguredContext( - context, - virtusizeParams.language - ) - binding.inpageButton.text = configuredContext?.getText(R.string.virtusize_button_text) - binding.privacyPolicyText.text = - configuredContext?.getText(R.string.virtusize_privacy_policy) - binding.inpageLoadingText.text = configuredContext?.getText(R.string.inpage_loading_text) - binding.inpageErrorText.text = configuredContext?.getText(R.string.inpage_long_error_text) - - setConfiguredDimensions(configuredContext) - - if (virtusizeParams.language == VirtusizeLanguage.JP) { - binding.inpageBottomText.includeFontPadding = true + /** + * Stops the cross fade animation + */ + private fun stopCrossFadeProductImageViews() { + // Remove the runnable from the handler + crossFadeRunnable?.let { crossFadeHandler.removeCallbacks(it) } + crossFadeRunnable = null + // Make sure the alpha values for product images are back to 1f if they got changed during the animation.= + binding.inpageUserProductImageView.alpha = 1f + binding.inpageStoreProductImageView.alpha = 1f } - binding.privacyPolicyText.setOnClickListener { - val intent = Intent( - Intent.ACTION_VIEW, - Uri.parse(configuredContext?.getString(R.string.virtusize_privacy_policy_link)) - ) - try { - context.startActivity(intent) - } catch (e: Exception) { - virtusizeMessageHandler.onError( - VirtusizeErrorType.PrivacyLinkNotOpen.virtusizeError( - extraMessage = e.localizedMessage + /** + * Fades in an image + * @param imageViewOne the image to be faded in + * @param imageViewTwo the image to be set invisible after the animation is done + */ + private fun fadeInAnimation( + imageViewOne: VirtusizeProductImageView, + imageViewTwo: VirtusizeProductImageView, + ) { + imageViewOne.apply { + alpha = 0f + visibility = VISIBLE + + animate() + .alpha(1f) + .setDuration(crossFadeAnimationDuration.toLong()) + .setListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + imageViewTwo.visibility = INVISIBLE + } + }, ) - ) } } - } - /** - * Sets up the text sizes and UI dimensions based on the configured context - */ - private fun setConfiguredDimensions(configuredContext: ContextWrapper?) { - val additionalSize = if (virtusizeParams.language == VirtusizeLanguage.EN) 2f.spToPx else 0f + /** + * Fades out an image + * @param imageView the image to be faded out + */ + private fun fadeOutAnimation(imageView: VirtusizeProductImageView) { + imageView.apply { + animate() + .alpha(0f) + .setDuration(crossFadeAnimationDuration.toLong()) + .setListener( + object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + crossFadeRunnable?.let { crossFadeHandler.postDelayed(it, 2500) } + } + }, + ) + } + } - if (messageTextSize != -1f) { - binding.inpageTopText.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - messageTextSize + 2f.spToPx + additionalSize - ) - binding.inpageLoadingText.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - messageTextSize + 6f.spToPx + additionalSize - ) - binding.inpageBottomText.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - messageTextSize + 6f.spToPx + additionalSize + /** + * Sets the background color of the size check button + */ + private fun setSizeCheckButtonBackgroundTint(color: Int) { + var drawable = ContextCompat.getDrawable(context, R.drawable.button_background_white) + drawable = DrawableCompat.wrap(drawable!!) + DrawableCompat.setTint(drawable, color) + ViewCompat.setBackground( + binding.inpageButton, + drawable, ) - binding.inpageErrorText.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - messageTextSize + additionalSize + } + + /** + * Sets up the text fonts, localization, and UI dimensions based on the configured context + */ + private fun setupConfiguredLocalization() { + FontUtils.setTypeFaces( + context, + mutableListOf( + binding.inpageTopText, + binding.inpageButton, + binding.privacyPolicyText, + ), + virtusizeParams.language, + FontUtils.FontType.REGULAR, ) - binding.privacyPolicyText.setTextSize( - TypedValue.COMPLEX_UNIT_PX, - messageTextSize + additionalSize + FontUtils.setTypeFaces( + context, + mutableListOf( + binding.inpageLoadingText, + binding.inpageBottomText, + ), + virtusizeParams.language, + FontUtils.FontType.BOLD, ) - } else { - configuredContext?.resources?.getDimension( - R.dimen.virtusize_inpage_standard_normal_textSize - )?.let { - binding.inpageTopText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + + val configuredContext = + VirtusizeUtils.getConfiguredContext( + context, + virtusizeParams.language, + ) + binding.inpageButton.text = configuredContext?.getText(R.string.virtusize_button_text) + binding.privacyPolicyText.text = + configuredContext?.getText(R.string.virtusize_privacy_policy) + binding.inpageLoadingText.text = + configuredContext?.getText( + R.string.inpage_loading_text, + ) + binding.inpageErrorText.text = + configuredContext?.getText( + R.string.inpage_long_error_text, + ) + + setConfiguredDimensions(configuredContext) + + if (virtusizeParams.language == VirtusizeLanguage.JP) { + binding.inpageBottomText.includeFontPadding = true } - configuredContext?.resources?.getDimension( - R.dimen.virtusize_inpage_standard_bold_textSize - )?.let { - binding.inpageLoadingText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) - binding.inpageBottomText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + binding.privacyPolicyText.setOnClickListener { + val intent = + Intent( + Intent.ACTION_VIEW, + Uri.parse( + configuredContext?.getString(R.string.virtusize_privacy_policy_link), + ), + ) + try { + context.startActivity(intent) + } catch (e: Exception) { + virtusizeMessageHandler.onError( + VirtusizeErrorType.PrivacyLinkNotOpen.virtusizeError( + extraMessage = e.localizedMessage, + ), + ) + } } + } - configuredContext?.resources?.getDimension( - R.dimen.virtusize_inpage_default_textSize - )?.let { - binding.inpageErrorText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + /** + * Sets up the text sizes and UI dimensions based on the configured context + */ + private fun setConfiguredDimensions(configuredContext: ContextWrapper?) { + val additionalSize = if (virtusizeParams.language == VirtusizeLanguage.EN) 2f.spToPx else 0f + + if (messageTextSize != -1f) { + binding.inpageTopText.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + messageTextSize + 2f.spToPx + additionalSize, + ) + binding.inpageLoadingText.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + messageTextSize + 6f.spToPx + additionalSize, + ) + binding.inpageBottomText.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + messageTextSize + 6f.spToPx + additionalSize, + ) + binding.inpageErrorText.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + messageTextSize + additionalSize, + ) + binding.privacyPolicyText.setTextSize( + TypedValue.COMPLEX_UNIT_PX, + messageTextSize + additionalSize, + ) + } else { + configuredContext?.resources?.getDimension( + R.dimen.virtusize_inpage_standard_normal_textSize, + )?.let { + binding.inpageTopText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + } + + configuredContext?.resources?.getDimension( + R.dimen.virtusize_inpage_standard_bold_textSize, + )?.let { + binding.inpageLoadingText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + binding.inpageBottomText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + } + + configuredContext?.resources?.getDimension( + R.dimen.virtusize_inpage_default_textSize, + )?.let { + binding.inpageErrorText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + } + + configuredContext?.resources?.getDimension( + R.dimen.virtusize_inpage_standard_privacy_policy_textSize, + )?.let { + binding.privacyPolicyText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + } } - configuredContext?.resources?.getDimension( - R.dimen.virtusize_inpage_standard_privacy_policy_textSize - )?.let { - binding.privacyPolicyText.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + if (buttonTextSize != -1f) { + val size = buttonTextSize + additionalSize + binding.inpageButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, size) + binding.inpageButton.rightDrawable( + R.drawable.ic_arrow_right_white, + 0.8f * size / 2, + 0.8f * size, + ) + } else { + configuredContext?.resources?.getDimension( + R.dimen.virtusize_inpage_default_textSize, + )?.let { + binding.inpageButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + } } - } - if (buttonTextSize != -1f) { - val size = buttonTextSize + additionalSize - binding.inpageButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, size) - binding.inpageButton.rightDrawable( - R.drawable.ic_arrow_right_white, - 0.8f * size / 2, - 0.8f * size - ) - } else { configuredContext?.resources?.getDimension( - R.dimen.virtusize_inpage_default_textSize + R.dimen.virtusize_inpage_standard_top_text_marginBottom, )?.let { - binding.inpageButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, it) + setupMargins(binding.inpageTopText, 0, 0, 0, it.toInt()) + binding.inpageTopText.setLineSpacing(it, 1f) + binding.inpageBottomText.setLineSpacing(it, 1f) } } - configuredContext?.resources?.getDimension( - R.dimen.virtusize_inpage_standard_top_text_marginBottom - )?.let { - setupMargins(binding.inpageTopText, 0, 0, 0, it.toInt()) - binding.inpageTopText.setLineSpacing(it, 1f) - binding.inpageBottomText.setLineSpacing(it, 1f) + /** + * Sets up the margins for a view + */ + private fun setupMargins( + view: View, + left: Int, + top: Int, + right: Int, + bottom: Int, + ) { + val layoutParams: MarginLayoutParams = view.layoutParams as MarginLayoutParams + layoutParams.setMargins(left, top, right, bottom) + view.requestLayout() } - } - /** - * Sets up the margins for a view - */ - private fun setupMargins(view: View, left: Int, top: Int, right: Int, bottom: Int) { - val layoutParams: MarginLayoutParams = view.layoutParams as MarginLayoutParams - layoutParams.setMargins(left, top, right, bottom) - view.requestLayout() - } - - /** - * Sets up the InPage Standard footer margins - */ - private fun setupInPageStandardFooterMargins(left: Int, top: Int, right: Int, bottom: Int) { - val layoutParams = LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT - ) - layoutParams.setMargins(left, top, right, bottom) - binding.inpageFooter.layoutParams = layoutParams + /** + * Sets up the InPage Standard footer margins + */ + private fun setupInPageStandardFooterMargins( + left: Int, + top: Int, + right: Int, + bottom: Int, + ) { + val layoutParams = + LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ) + layoutParams.setMargins(left, top, right, bottom) + binding.inpageFooter.layoutParams = layoutParams + } } -} diff --git a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageStandardViewModel.kt b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageStandardViewModel.kt index 06216232..dc1cc9e0 100644 --- a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageStandardViewModel.kt +++ b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageStandardViewModel.kt @@ -9,9 +9,8 @@ import com.virtusize.android.data.remote.Product import kotlinx.coroutines.launch internal class VirtusizeInPageStandardViewModel( - private val virtusizeRepository: VirtusizeRepository + private val virtusizeRepository: VirtusizeRepository, ) : ViewModel() { - val productNetworkImageLiveData = MutableLiveData>() val productPlaceholderImageLiveData = diff --git a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageView.kt b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageView.kt index 7397acf0..6aaa359a 100644 --- a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageView.kt +++ b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeInPageView.kt @@ -7,34 +7,39 @@ import android.widget.RelativeLayout /** * An abstract class representing the VirtusizeView that is a RelativeLayout */ -abstract class VirtusizeInPageView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : VirtusizeView, RelativeLayout(context, attrs, defStyleAttr) { - // The text size of the message to be set - var messageTextSize: Float = -1f - set(value) { - field = value - setStyle() - } +abstract class VirtusizeInPageView + @JvmOverloads + constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, + ) : VirtusizeView, RelativeLayout(context, attrs, defStyleAttr) { + // The text size of the message to be set + var messageTextSize: Float = -1f + set(value) { + field = value + setStyle() + } - // The text size of the Check Size button to be set - var buttonTextSize: Float = -1f - set(value) { - field = value - setStyle() - } + // The text size of the Check Size button to be set + var buttonTextSize: Float = -1f + set(value) { + field = value + setStyle() + } - internal abstract fun setStyle() + internal abstract fun setStyle() - /** - * An abstract function to set the recommendation text with the associated external product ID - */ - internal abstract fun setRecommendationText(externalProductId: String, text: String) + /** + * An abstract function to set the recommendation text with the associated external product ID + */ + internal abstract fun setRecommendationText( + externalProductId: String, + text: String, + ) - /** - * An abstract function to show the InPage error screen with the associated external product ID - */ - internal abstract fun showInPageError(externalProductId: String?) -} + /** + * An abstract function to show the InPage error screen with the associated external product ID + */ + internal abstract fun showInPageError(externalProductId: String?) + } diff --git a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeProductImageView.kt b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeProductImageView.kt index 7074dd32..a2c63200 100644 --- a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeProductImageView.kt +++ b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeProductImageView.kt @@ -17,13 +17,13 @@ import com.virtusize.android.util.getDrawableResourceByName */ internal class VirtusizeProductImageView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) { - // The product type to determine the UI style private var productImageType: ProductImageType = ProductImageType.STORE // This enum contains the possible product types enum class ProductImageType { - STORE, USER + STORE, + USER, } private val binding = ViewProductImageBinding.inflate(LayoutInflater.from(context), this) @@ -31,33 +31,35 @@ internal class VirtusizeProductImageView(context: Context, attrs: AttributeSet) init { orientation = VERTICAL - val typedArray = context.obtainStyledAttributes( - attrs, - R.styleable.VirtusizeProductImageView, - 0, - 0 - ) - - productImageType = ProductImageType.values()[ - typedArray.getInt( - R.styleable.VirtusizeProductImageView_productImageType, - 0 + val typedArray = + context.obtainStyledAttributes( + attrs, + R.styleable.VirtusizeProductImageView, + 0, + 0, ) - ] + + productImageType = + ProductImageType.values()[ + typedArray.getInt( + R.styleable.VirtusizeProductImageView_productImageType, + 0, + ), + ] if (productImageType == ProductImageType.USER) { binding.inpageBorderImageView.setImageDrawable( ContextCompat.getDrawable( context, - R.drawable.ic_image_border_green_dash - ) + R.drawable.ic_image_border_green_dash, + ), ) } else { binding.inpageBorderImageView.setImageDrawable( ContextCompat.getDrawable( context, - R.drawable.ic_image_border_gray - ) + R.drawable.ic_image_border_gray, + ), ) } @@ -78,17 +80,20 @@ internal class VirtusizeProductImageView(context: Context, attrs: AttributeSet) * @param productType the product type, which is fetched from the store product info * @param style the product style, which is fetched from the store product info */ - fun setProductPlaceHolderImage(productType: Int?, style: String?) { + fun setProductPlaceHolderImage( + productType: Int?, + style: String?, + ) { if (productImageType == ProductImageType.STORE) { binding.inpageProductCardView.setCardBackgroundColor( ContextCompat.getColor( context, - R.color.color_gray_200 - ) + R.color.color_gray_200, + ), ) } binding.inpageProductImageView.setImageDrawable( - getProductPlaceholderImage(productType, style) + getProductPlaceholderImage(productType, style), ) binding.inpageProductImageView.setPadding(6.dpInPx, 6.dpInPx, 6.dpInPx, 6.dpInPx) } @@ -99,13 +104,18 @@ internal class VirtusizeProductImageView(context: Context, attrs: AttributeSet) * @param style the product style, which is fetched from the store product info * @return a Drawable of product placeholder image */ - private fun getProductPlaceholderImage(productType: Int?, style: String?): Drawable? { - var productPlaceholderImage = context.getDrawableResourceByName( - "ic_product_type_$productType" - ) - val productTypeImageWithStyle = context.getDrawableResourceByName( - "ic_product_type_${productType}_$style" - ) + private fun getProductPlaceholderImage( + productType: Int?, + style: String?, + ): Drawable? { + var productPlaceholderImage = + context.getDrawableResourceByName( + "ic_product_type_$productType", + ) + val productTypeImageWithStyle = + context.getDrawableResourceByName( + "ic_product_type_${productType}_$style", + ) if (productTypeImageWithStyle != null) { productPlaceholderImage = productTypeImageWithStyle } @@ -115,8 +125,8 @@ internal class VirtusizeProductImageView(context: Context, attrs: AttributeSet) productPlaceholderImage?.setTint( ContextCompat.getColor( context, - R.color.virtusizeBlack - ) + R.color.virtusizeBlack, + ), ) } return productPlaceholderImage diff --git a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeView.kt b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeView.kt index d7d48bd5..6eda9e90 100644 --- a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeView.kt +++ b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeView.kt @@ -15,10 +15,13 @@ import com.virtusize.android.util.VirtusizeUtils interface VirtusizeView { // The product which is bounded with this Virtusize var clientProduct: VirtusizeProduct? + // The parameter object to be passed to the Virtusize web app var virtusizeParams: VirtusizeParams + // Receives Virtusize messages var virtusizeMessageHandler: VirtusizeMessageHandler + // The Virtusize view that opens when the view is clicked var virtusizeDialogFragment: VirtusizeWebViewFragment @@ -31,7 +34,7 @@ interface VirtusizeView { fun initialSetup( product: VirtusizeProduct, params: VirtusizeParams, - messageHandler: VirtusizeMessageHandler + messageHandler: VirtusizeMessageHandler, ) { clientProduct = product virtusizeParams = params @@ -62,13 +65,16 @@ interface VirtusizeView { /** * A clickable function to open the Virtusize WebView */ - fun openVirtusizeWebView(context: Context, product: VirtusizeProduct) { + fun openVirtusizeWebView( + context: Context, + product: VirtusizeProduct, + ) { VirtusizeUtils.openVirtusizeWebView( context, virtusizeParams, virtusizeDialogFragment, product, - virtusizeMessageHandler + virtusizeMessageHandler, ) } } diff --git a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeWebViewFragment.kt b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeWebViewFragment.kt index 459b759c..8b745602 100644 --- a/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeWebViewFragment.kt +++ b/virtusize/src/main/java/com/virtusize/android/ui/VirtusizeWebViewFragment.kt @@ -30,7 +30,6 @@ import com.virtusize.android.util.Constants import org.json.JSONObject class VirtusizeWebViewFragment : DialogFragment() { - private var virtusizeWebAppUrl = "https://static.api.virtusize.jp/a/aoyama/latest/sdk-webview.html" private var vsParamsFromSDKScript = "" @@ -49,7 +48,7 @@ class VirtusizeWebViewFragment : DialogFragment() { VirtusizeAuth.handleVirtusizeSNSAuthResult( binding.webView, result.resultCode, - result.data + result.data, ) } @@ -67,14 +66,17 @@ class VirtusizeWebViewFragment : DialogFragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? + savedInstanceState: Bundle?, ): View { binding = WebActivityBinding.inflate(inflater, container, false) return binding.root } @SuppressLint("SetJavaScriptEnabled") - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + override fun onViewCreated( + view: View, + savedInstanceState: Bundle?, + ) { super.onViewCreated(view, savedInstanceState) dialog?.window?.attributes?.windowAnimations = R.style.VirtusizeDialogFragmentAnimation // Enable JavaScript in the web view @@ -86,74 +88,87 @@ class VirtusizeWebViewFragment : DialogFragment() { // Add the Javascript interface to interface the web app with the web view binding.webView.addJavascriptInterface(WebAppInterface(), Constants.JS_BRIDGE_NAME) // Set up the web view client that adds JavaScript scripts for the interaction between the SDK and the web - binding.webView.webViewClient = object : WebViewClient() { - override fun onPageFinished(view: WebView?, url: String?) { - if (url != null && url.contains(virtusizeWebAppUrl)) { - binding.webView.evaluateJavascript(vsParamsFromSDKScript, null) - binding.webView.evaluateJavascript( - "javascript:window.virtusizeSNSEnabled = true;", - null - ) - getBrowserIDFromCookies()?.let { bid -> - if (bid != sharedPreferencesHelper.getBrowserId()) { - sharedPreferencesHelper.storeBrowserId(bid) + binding.webView.webViewClient = + object : WebViewClient() { + override fun onPageFinished( + view: WebView?, + url: String?, + ) { + if (url != null && url.contains(virtusizeWebAppUrl)) { + binding.webView.evaluateJavascript(vsParamsFromSDKScript, null) + binding.webView.evaluateJavascript( + "javascript:window.virtusizeSNSEnabled = true;", + null, + ) + getBrowserIDFromCookies()?.let { bid -> + if (bid != sharedPreferencesHelper.getBrowserId()) { + sharedPreferencesHelper.storeBrowserId(bid) + } } } } - } - override fun onLoadResource(view: WebView?, url: String?) { - super.onLoadResource(view, url) - // To prevent multiple views in the WebView when a user selects a different display language - if (url != null && url.contains("i18n")) { - binding.webView.removeAllViews() + override fun onLoadResource( + view: WebView?, + url: String?, + ) { + super.onLoadResource(view, url) + // To prevent multiple views in the WebView when a user selects a different display language + if (url != null && url.contains("i18n")) { + binding.webView.removeAllViews() + } } } - } - binding.webView.webChromeClient = object : WebChromeClient() { - override fun onCreateWindow( - view: WebView, - dialog: Boolean, - userGesture: Boolean, - resultMsg: Message - ): Boolean { - if (resultMsg.obj != null && resultMsg.obj is WebView.WebViewTransport) { - val popupWebView = WebView(view.context) - popupWebView.settings.javaScriptEnabled = true - popupWebView.settings.javaScriptCanOpenWindowsAutomatically = true - popupWebView.settings.setSupportMultipleWindows(true) - popupWebView.settings.userAgentString = System.getProperty("http.agent") - popupWebView.webViewClient = object : WebViewClient() { - override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { - if (VirtusizeURLCheck.isExternalLinkFromVirtusize(url)) { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - try { - startActivity(intent) - } finally { - return true + binding.webView.webChromeClient = + object : WebChromeClient() { + override fun onCreateWindow( + view: WebView, + dialog: Boolean, + userGesture: Boolean, + resultMsg: Message, + ): Boolean { + if (resultMsg.obj != null && resultMsg.obj is WebView.WebViewTransport) { + val popupWebView = WebView(view.context) + popupWebView.settings.javaScriptEnabled = true + popupWebView.settings.javaScriptCanOpenWindowsAutomatically = true + popupWebView.settings.setSupportMultipleWindows(true) + popupWebView.settings.userAgentString = System.getProperty("http.agent") + popupWebView.webViewClient = + object : WebViewClient() { + override fun shouldOverrideUrlLoading( + view: WebView, + url: String, + ): Boolean { + if (VirtusizeURLCheck.isExternalLinkFromVirtusize(url)) { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + try { + startActivity(intent) + } finally { + return true + } + } + return VirtusizeAuth.isSNSAuthUrl( + requireContext(), + virtusizeSNSAuthLauncher, + url, + ) } } - return VirtusizeAuth.isSNSAuthUrl( - requireContext(), - virtusizeSNSAuthLauncher, - url - ) - } - } - popupWebView.webChromeClient = object : WebChromeClient() { - override fun onCloseWindow(window: WebView) { - binding.webView.removeAllViews() - } + popupWebView.webChromeClient = + object : WebChromeClient() { + override fun onCloseWindow(window: WebView) { + binding.webView.removeAllViews() + } + } + val transport = resultMsg.obj as WebView.WebViewTransport + binding.webView.addView(popupWebView) + transport.webView = popupWebView + resultMsg.sendToTarget() } - val transport = resultMsg.obj as WebView.WebViewTransport - binding.webView.addView(popupWebView) - transport.webView = popupWebView - resultMsg.sendToTarget() + return true } - return true } - } binding.webView.setOnKeyListener { v, keyCode, event -> if (keyCode == KeyEvent.KEYCODE_BACK && event.action == MotionEvent.ACTION_UP) { @@ -163,9 +178,11 @@ class VirtusizeWebViewFragment : DialogFragment() { binding.webView.removeAllViews() binding.webView.reload() } - else -> binding.webView.evaluateJavascript( - backButtonClickEventFromSDKScript, null - ) + else -> + binding.webView.evaluateJavascript( + backButtonClickEventFromSDKScript, + null, + ) } } true @@ -236,7 +253,6 @@ class VirtusizeWebViewFragment : DialogFragment() { * The JavaScript interface to interact the web app with the web view */ private inner class WebAppInterface { - /** * Receives any event information from the Virtusize web app * @param eventInfo The String value of the event info @@ -258,7 +274,7 @@ class VirtusizeWebViewFragment : DialogFragment() { binding.webView.post { binding.webView.evaluateJavascript( "localStorage.setItem('acceptedPrivacyPolicy','true');", - null + null, ) } } diff --git a/virtusize/src/main/java/com/virtusize/android/util/FontUtils.kt b/virtusize/src/main/java/com/virtusize/android/util/FontUtils.kt index 225ce0aa..543eb924 100644 --- a/virtusize/src/main/java/com/virtusize/android/util/FontUtils.kt +++ b/virtusize/src/main/java/com/virtusize/android/util/FontUtils.kt @@ -6,14 +6,13 @@ import com.virtusize.android.data.local.VirtusizeLanguage // The object that wraps Font utility functions internal object FontUtils { - /** * This enum contains all available font names used in this SDK */ enum class FontName(val value: String) { ROBOTO("roboto"), NOTO_SANS_CJK_JP("noto_sans_cjk_jp"), - NOTO_SANS_CJK_KR("noto_sans_cjk_kr") + NOTO_SANS_CJK_KR("noto_sans_cjk_kr"), } /** @@ -21,7 +20,7 @@ internal object FontUtils { */ enum class FontType(val value: String) { REGULAR("_regular"), - BOLD("_bold") + BOLD("_bold"), } /** @@ -31,7 +30,7 @@ internal object FontUtils { context: Context, textView: TextView, language: VirtusizeLanguage?, - fontType: FontType + fontType: FontType, ) { setTypeFaces(context, mutableListOf(textView), language, fontType) } @@ -43,7 +42,7 @@ internal object FontUtils { context: Context, textViews: List, language: VirtusizeLanguage?, - fontType: FontType + fontType: FontType, ) { when (language) { VirtusizeLanguage.EN -> { @@ -67,7 +66,7 @@ internal object FontUtils { context: Context, textViews: List, fontName: FontName, - fontType: FontType + fontType: FontType, ) { for (textView in textViews) { setTypeFace(context, textView, fontName, fontType) @@ -81,7 +80,7 @@ internal object FontUtils { context: Context, textView: TextView, fontName: FontName, - fontType: FontType + fontType: FontType, ) { textView.typeface = context.getTypefaceByName(fontName.value + fontType.value) } diff --git a/virtusize/src/main/java/com/virtusize/android/util/VirtusizeUtils.kt b/virtusize/src/main/java/com/virtusize/android/util/VirtusizeUtils.kt index b26bccc7..631082c7 100644 --- a/virtusize/src/main/java/com/virtusize/android/util/VirtusizeUtils.kt +++ b/virtusize/src/main/java/com/virtusize/android/util/VirtusizeUtils.kt @@ -23,7 +23,6 @@ import kotlin.math.abs // The object that wraps Virtusize utility functions internal object VirtusizeUtils { - // The context wrapper that is configured to a designated locale class ConfiguredContext(base: Context?) : ContextWrapper(base) @@ -32,7 +31,10 @@ internal object VirtusizeUtils { * @param context the base application Context * @param locale the locale to switch to */ - private fun configureLocale(context: Context, locale: Locale?): ContextWrapper? { + private fun configureLocale( + context: Context, + locale: Locale?, + ): ContextWrapper? { var updatedContext = context val resources = context.resources val configuration = resources.configuration @@ -54,7 +56,10 @@ internal object VirtusizeUtils { /** * Gets configured context base on the language that clients set up with the Virtusize Builder in the application */ - fun getConfiguredContext(context: Context, language: VirtusizeLanguage?): ContextWrapper? { + fun getConfiguredContext( + context: Context, + language: VirtusizeLanguage?, + ): ContextWrapper? { return when (language) { VirtusizeLanguage.EN -> configureLocale(context, Locale.ENGLISH) VirtusizeLanguage.JP -> configureLocale(context, Locale.JAPAN) @@ -73,7 +78,7 @@ internal object VirtusizeUtils { fun findBestFitProductSize( userProducts: List?, storeProduct: Product?, - productTypes: List? + productTypes: List?, ): SizeComparisonRecommendedSize? { if (userProducts == null || storeProduct == null || productTypes == null) { return null @@ -87,11 +92,12 @@ internal object VirtusizeUtils { compatibleUserProducts.iterator().forEach { userProduct -> val userProductSize = userProduct.sizes[0] storeProduct.sizes.iterator().forEach { storeProductSize -> - val productComparisonFitInfo = getProductComparisonFitInfo( - userProductSize, - storeProductSize, - storeProductType.weights - ) + val productComparisonFitInfo = + getProductComparisonFitInfo( + userProductSize, + storeProductSize, + storeProductType.weights, + ) if ( productComparisonFitInfo.fitScore > sizeComparisonRecommendedSize.bestFitScore ) { @@ -119,7 +125,7 @@ internal object VirtusizeUtils { fun getProductComparisonFitInfo( userProductSize: ProductSize, storeProductSize: ProductSize, - storeProductTypeScoreWeights: Set + storeProductTypeScoreWeights: Set, ): ProductComparisonFitInfo { var rawScore = 0f var isSmaller: Boolean? = null @@ -134,7 +140,7 @@ internal object VirtusizeUtils { if (userProductSizeMeasurement != null && storeProductSizeMeasurement != null) { rawScore += abs( - weight.value * (userProductSizeMeasurement - storeProductSizeMeasurement) + weight.value * (userProductSizeMeasurement - storeProductSizeMeasurement), ) isSmaller = isSmaller ?: (userProductSizeMeasurement - storeProductSizeMeasurement > 0) @@ -155,7 +161,7 @@ internal object VirtusizeUtils { virtusizeParams: VirtusizeParams?, virtusizeDialogFragment: VirtusizeWebViewFragment, product: VirtusizeProduct, - messageHandler: VirtusizeMessageHandler + messageHandler: VirtusizeMessageHandler, ) { val fragmentTransaction = (context as FragmentActivity).supportFragmentManager.beginTransaction() @@ -169,7 +175,7 @@ internal object VirtusizeUtils { virtusizeParams?.let { params -> args.putString( Constants.VIRTUSIZE_PARAMS_SCRIPT_KEY, - "javascript:vsParamsFromSDK(${params.vsParamsString(product)})" + "javascript:vsParamsFromSDK(${params.vsParamsString(product)})", ) } args.putParcelable(Constants.VIRTUSIZE_PRODUCT_KEY, product) diff --git a/virtusize/src/test/java/com/virtusize/android/data/parsers/StoreJsonParserTest.kt b/virtusize/src/test/java/com/virtusize/android/data/parsers/StoreJsonParserTest.kt deleted file mode 100644 index f9b4ef4f..00000000 --- a/virtusize/src/test/java/com/virtusize/android/data/parsers/StoreJsonParserTest.kt +++ /dev/null @@ -1,51 +0,0 @@ -package com.virtusize.android.data.parsers - -import com.google.common.truth.Truth.assertThat -import com.virtusize.android.data.remote.Store -import com.virtusize.android.fixtures.TestFixtures -import org.junit.Test - -class StoreJsonParserTest { - - @Test - fun parse_hasFullInfo_shouldReturnExpectedObject() { - val actualStore = StoreJsonParser().parse(TestFixtures.STORE_WITH_FULL_INFO) - - val expectedStore = Store( - 2, - "https://www.survey.com/s/xxxxxx", - "Virtusize", - "virtusize", - 2, - "test_apiKey", - "2011-01-01T00:00:00Z", - "2020-04-20T02:33:58Z", - "2018-05-29 04:32:45", - false, - "KR" - ) - - assertThat(actualStore).isEqualTo(expectedStore) - } - - @Test - fun parse_hasNullValues_shouldReturnExpectedObject() { - val actualStore = StoreJsonParser().parse(TestFixtures.STORE_WITH_NULL_VALUES) - - val expectedStore = Store( - 2, - "https://www.survey.com/s/xxxxxx", - "Virtusize", - "virtusize", - 2, - "test_apiKey", - "2011-01-01T00:00:00Z", - "2020-04-20T02:33:58Z", - "", - false, - "JP" - ) - - assertThat(actualStore).isEqualTo(expectedStore) - } -} diff --git a/virtusize/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt b/virtusize/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt index b2997cda..1b7c9ed7 100644 --- a/virtusize/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt +++ b/virtusize/src/test/java/com/virtusize/android/fixtures/ProductFixtures.kt @@ -12,126 +12,129 @@ import org.json.JSONArray import org.json.JSONObject internal object ProductFixtures { - private val PRODUCT_TYPE_ID_ONE_JSON_OBJECT_STRING = """ - { - "id": 1, - "name": "dress", - "weights": { - "bust": 1, - "waist": 1, - "height": 0.25 - }, - "compatibleWith": [ - 1, - 16 - ] - } + { + "id": 1, + "name": "dress", + "weights": { + "bust": 1, + "waist": 1, + "height": 0.25 + }, + "compatibleWith": [ + 1, + 16 + ] + } """.trimIndent() private val PRODUCT_TYPE_ID_TWO_JSON_OBJECT_STRING = """ - { - "id": 2, - "name": "shirt", - "weights": { - "bust": 2, - "height": 0.5, - "sleeve": 1 - }, - "compatibleWith":[ - 2 - ] - } + { + "id": 2, + "name": "shirt", + "weights": { + "bust": 2, + "height": 0.5, + "sleeve": 1 + }, + "compatibleWith":[ + 2 + ] + } """.trimIndent() private val PRODUCT_TYPE_ID_EIGHT_JSON_OBJECT_STRING = """ - { - "id": 8, - "name": "jacket", - "weights": { - "bust": 2, - "height": 1, - "sleeve": 1 - }, - "compatibleWith": [ - 8, - 14 - ] - } + { + "id": 8, + "name": "jacket", + "weights": { + "bust": 2, + "height": 1, + "sleeve": 1 + }, + "compatibleWith": [ + 8, + 14 + ] + } """.trimIndent() private val PRODUCT_TYPE_ID_EIGHTEEN_JSON_OBJECT_STRING = """ - { - "id":18, - "name": "bag", - "weights":{ - "depth":1, - "width":2, - "height":1 - }, - "compatibleWith":[ - 18, - 19, - 25, - 26 - ] - } + { + "id":18, + "name": "bag", + "weights":{ + "depth":1, + "width":2, + "height":1 + }, + "compatibleWith":[ + 18, + 19, + 25, + 26 + ] + } """.trimIndent() - val PRODUCT_TYPE_JSON_ARRAY = JSONArray( - """ + val PRODUCT_TYPE_JSON_ARRAY = + JSONArray( + """ [ $PRODUCT_TYPE_ID_ONE_JSON_OBJECT_STRING, $PRODUCT_TYPE_ID_TWO_JSON_OBJECT_STRING, $PRODUCT_TYPE_ID_EIGHT_JSON_OBJECT_STRING, $PRODUCT_TYPE_ID_EIGHTEEN_JSON_OBJECT_STRING ] - """.trimIndent() - ) + """.trimIndent(), + ) - fun productTypes() = run { - val productTypes: MutableList = mutableListOf() - for (i in 0 until PRODUCT_TYPE_JSON_ARRAY.length()) { - ProductTypeJsonParser().parse(PRODUCT_TYPE_JSON_ARRAY[i] as JSONObject)?.let { - productTypes.add(it) + fun productTypes() = + run { + val productTypes: MutableList = mutableListOf() + for (i in 0 until PRODUCT_TYPE_JSON_ARRAY.length()) { + ProductTypeJsonParser().parse(PRODUCT_TYPE_JSON_ARRAY[i] as JSONObject)?.let { + productTypes.add(it) + } } + productTypes } - productTypes - } fun storeProduct( productType: Int = 8, - sizeList: List = mutableListOf( - ProductSize( - "38", - mutableSetOf( - Measurement("height", 760), - Measurement("bust", 660), - Measurement("sleeve", 845) - ) + sizeList: List = + mutableListOf( + ProductSize( + "38", + mutableSetOf( + Measurement("height", 760), + Measurement("bust", 660), + Measurement("sleeve", 845), + ), + ), + ProductSize( + "36", + mutableSetOf( + Measurement("height", 750), + Measurement("bust", 645), + Measurement("sleeve", 825), + ), + ), ), - ProductSize( - "36", - mutableSetOf( - Measurement("height", 750), - Measurement("bust", 645), - Measurement("sleeve", 825) - ) - ) - ), brand: String = "Virtusize", - modelInfo: Map? = mutableMapOf( - "hip" to 85, - "size" to "38", - "waist" to 56, - "bust" to 78, - "height" to 165 - ), - gender: String? = "female" + modelInfo: Map? = + mutableMapOf( + "hip" to 85, + "size" to "38", + "waist" to 56, + "bust" to 78, + "height" to 165, + ), + gender: String? = "female", ): Product { return Product( 7110384, @@ -151,16 +154,17 @@ internal object ProductFixtures { modelInfo, "regular", "fashionable", - BrandSizing("large", false) + BrandSizing("large", false), ), brand, - gender - ) + gender, + ), ) } - val STORE_PRODUCT_INFO_JSON_DATA = JSONObject( - """ + val STORE_PRODUCT_INFO_JSON_DATA = + JSONObject( + """ { "id":${TestFixtures.PRODUCT_ID}, "sizes":[ @@ -286,79 +290,80 @@ internal object ProductFixtures { "storeProduct":${TestFixtures.PRODUCT_ID} } } - """.trimIndent() - ) + """.trimIndent(), + ) private val USER_PRODUCT_ONE_JSON_STRING = """ - { - "id": 123456, - "sizes": [ - { - "name": "S", - "measurements": { - "height": 1000, - "bust": 400, - "waist": 340, - "hip": null, - "hem": null, - "waistHeight": null - } - } - ], - "productType": 11, - "created": "2020-09-14T11:06:00Z", - "updated": "2020-09-14T11:06:00Z", - "name": "Test Womenswear Strapless Dress", - "cloudinaryPublicId": null, - "deleted": false, - "isFavorite": false, - "wardrobe": 123, - "orderItem": null, - "store": null - } + { + "id": 123456, + "sizes": [ + { + "name": "S", + "measurements": { + "height": 1000, + "bust": 400, + "waist": 340, + "hip": null, + "hem": null, + "waistHeight": null + } + } + ], + "productType": 11, + "created": "2020-09-14T11:06:00Z", + "updated": "2020-09-14T11:06:00Z", + "name": "Test Womenswear Strapless Dress", + "cloudinaryPublicId": null, + "deleted": false, + "isFavorite": false, + "wardrobe": 123, + "orderItem": null, + "store": null + } """.trimIndent() private val USER_PRODUCT_TWO_JSON_STRING = """ - { - "id": 654321, - "sizes": [ - { - "name": null, - "measurements": { - "height": 820, - "bust": 520, - "sleeve": 930, - "collar": null, - "shoulder": null, - "waist": null, - "hem": null, - "bicep": null - } + { + "id": 654321, + "sizes": [ + { + "name": null, + "measurements": { + "height": 820, + "bust": 520, + "sleeve": 930, + "collar": null, + "shoulder": null, + "waist": null, + "hem": null, + "bicep": null } - ], - "productType": 2, - "created": "2020-07-22T10:22:19Z", - "updated": "2020-07-22T10:22:19Z", - "name": "test2", - "cloudinaryPublicId": null, - "deleted": false, - "isFavorite": true, - "wardrobe": 123, - "orderItem": null, - "store": 2 - } + } + ], + "productType": 2, + "created": "2020-07-22T10:22:19Z", + "updated": "2020-07-22T10:22:19Z", + "name": "test2", + "cloudinaryPublicId": null, + "deleted": false, + "isFavorite": true, + "wardrobe": 123, + "orderItem": null, + "store": 2 + } """.trimIndent() - val USER_PRODUCT_JSON_ARRAY = JSONArray( - """ + val USER_PRODUCT_JSON_ARRAY = + JSONArray( + """ [ $USER_PRODUCT_ONE_JSON_STRING, $USER_PRODUCT_TWO_JSON_STRING ] - """.trimIndent() - ) + """.trimIndent(), + ) val EMPTY_PRODUCT_JSON_ARRAY = JSONArray("[]") 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 60a7310a..bd883005 100644 --- a/virtusize/src/test/java/com/virtusize/android/fixtures/TestFixtures.kt +++ b/virtusize/src/test/java/com/virtusize/android/fixtures/TestFixtures.kt @@ -9,7 +9,6 @@ import com.virtusize.android.data.remote.UserBodyProfile import org.json.JSONObject internal object TestFixtures { - const val API_KEY = "test_apiKey" const val USER_ID = "123" const val STORE_ID = 2 @@ -19,8 +18,9 @@ internal object TestFixtures { val EMPTY_JSON_DATA = JSONObject("{}") - val PRODUCT_DATA_CHECK = JSONObject( - """ + val PRODUCT_DATA_CHECK = + JSONObject( + """ { "data":{ "productTypeName": "pants", @@ -37,11 +37,12 @@ internal object TestFixtures { "name": "backend-checked-product", "productId": "$EXTERNAL_ID" } - """.trimIndent() - ) + """.trimIndent(), + ) - val INVALID_PRODUCT_DATA_CHECK = JSONObject( - """ + val INVALID_PRODUCT_DATA_CHECK = + JSONObject( + """ { "data": { "productDataId": null, @@ -53,15 +54,16 @@ internal object TestFixtures { "name": "backend-checked-product", "productId": "63434343" } - """.trimIndent() - ) + """.trimIndent(), + ) val PRODUCT_CHECK = ProductCheckJsonParser().parse(PRODUCT_DATA_CHECK) val VIRTUSIZE_PRODUCT = VirtusizeProduct(EXTERNAL_ID, "http://image.com/xxx.jpg", PRODUCT_CHECK) - val PRODUCT_DATA_CHECK_DATA = JSONObject( - """ + val PRODUCT_DATA_CHECK_DATA = + JSONObject( + """ { "productTypeName": "pants", "storeName": "virtusize", @@ -74,22 +76,24 @@ internal object TestFixtures { "should_see_ph_tooltip": false } } - """.trimIndent() - ) + """.trimIndent(), + ) - val PRODUCT_META_DATA_HINTS = JSONObject( - """ + val PRODUCT_META_DATA_HINTS = + JSONObject( + """ { "apiKey": "test_apiKey", "imageUrl": "http://www.test.com/goods/31/12/11/71/1234_COL_COL02_570.jpg", "cloudinaryPublicId": "test_cloudinaryPublicId", "externalProductId": "$EXTERNAL_ID" } - """.trimIndent() - ) + """.trimIndent(), + ) - val USER_SAW_PRODUCT_EVENT_RESPONSE = JSONObject( - """ + val USER_SAW_PRODUCT_EVENT_RESPONSE = + JSONObject( + """ { "apiKey": "$API_KEY", "name": "user-saw-product", @@ -118,11 +122,12 @@ internal object TestFixtures { "browserLanguage": "en", "@timestamp": "2020-06-16T13:06:24.842988Z" } - """.trimIndent() - ) + """.trimIndent(), + ) - val STORE_WITH_FULL_INFO = JSONObject( - """ + val STORE_WITH_FULL_INFO = + JSONObject( + """ { "id": 2, "surveyLink": "https://www.survey.com/s/xxxxxx", @@ -136,11 +141,12 @@ internal object TestFixtures { "typemapperEnabled": false, "region": "KR" } - """.trimIndent() - ) + """.trimIndent(), + ) - val STORE_WITH_NULL_VALUES = JSONObject( - """ + val STORE_WITH_NULL_VALUES = + JSONObject( + """ { "id": 2, "surveyLink": "https://www.survey.com/s/xxxxxx", @@ -154,107 +160,111 @@ internal object TestFixtures { "typemapperEnabled": false, "region": null } - """.trimIndent() - ) + """.trimIndent(), + ) - val VIRTUSIZE_ORDER = VirtusizeOrder( - "888400111032", - mutableListOf( - VirtusizeOrderItem( - "P001", - "L", - "Large", - "P001_SIZEL_RED", - "http://images.example.com/products/P001/red/image1xl.jpg", - "Red", - "W", - 5100.00, - "JPY", - 1, - "http://example.com/products/P001" - ) + val VIRTUSIZE_ORDER = + VirtusizeOrder( + "888400111032", + mutableListOf( + VirtusizeOrderItem( + "P001", + "L", + "Large", + "P001_SIZEL_RED", + "http://images.example.com/products/P001/red/image1xl.jpg", + "Red", + "W", + 5100.00, + "JPY", + 1, + "http://example.com/products/P001", + ), + ), ) - ) - val USER_BODY_JSONObject = JSONObject( - """ - { - "wardrobe": "1234567", - "gender": "female", - "age": 32, - "height": 1630, - "weight": "50.00", - "braSize": {}, - "concernAreas": {}, - "bodyData": { - "hip": 830, - "bust": 755, - "neck": 300, - "rise": 215, - "bicep": 220, - "thigh": 480, - "waist": 630, - "inseam": 700, - "sleeve": 720, - "shoulder": 370, - "hipWidth": 300, - "bustWidth": 245, - "hipHeight": 750, - "headHeight": 215, - "kneeHeight": 395, - "waistWidth": 225, - "waistHeight": 920, - "armpitHeight": 1130, - "sleeveLength": 520, - "shoulderWidth": 340, - "shoulderHeight": 1240 - } - } - """.trimIndent() - ) + val USER_BODY_JSONObject = + JSONObject( + """ + { + "wardrobe": "1234567", + "gender": "female", + "age": 32, + "height": 1630, + "weight": "50.00", + "braSize": {}, + "concernAreas": {}, + "bodyData": { + "hip": 830, + "bust": 755, + "neck": 300, + "rise": 215, + "bicep": 220, + "thigh": 480, + "waist": 630, + "inseam": 700, + "sleeve": 720, + "shoulder": 370, + "hipWidth": 300, + "bustWidth": 245, + "hipHeight": 750, + "headHeight": 215, + "kneeHeight": 395, + "waistWidth": 225, + "waistHeight": 920, + "armpitHeight": 1130, + "sleeveLength": 520, + "shoulderWidth": 340, + "shoulderHeight": 1240 + } + } + """.trimIndent(), + ) - val NULL_USER_BODY_PROFILE = JSONObject( - """ - { - "gender": "", - "age": null, - "height": null, - "weight": null, - "braSize": null, - "concernAreas": null, - "bodyData": null - } - """.trimIndent() - ) + val NULL_USER_BODY_PROFILE = + JSONObject( + """ + { + "gender": "", + "age": null, + "height": null, + "weight": null, + "braSize": null, + "concernAreas": null, + "bodyData": null + } + """.trimIndent(), + ) - val userBodyProfile = UserBodyProfile( - "female", - 32, - 1630, - "50.00", - mutableSetOf( - Measurement("hip", 830), - Measurement("hip", 830), - Measurement("bust", 755), - Measurement("neck", 300), - Measurement("rise", 215), - Measurement("bicep", 220), - Measurement("thigh", 480), - Measurement("waist", 630), - Measurement("inseam", 700), - Measurement("sleeve", 720), - Measurement("shoulder", 370), - Measurement("hipWidth", 300), - Measurement("bustWidth", 245), - Measurement("hipHeight", 750), - Measurement("headHeight", 215), - Measurement("kneeHeight", 395), - Measurement("waistWidth", 225), - Measurement("waistHeight", 920), - Measurement("armpitHeight", 1130), - Measurement("sleeveLength", 520), - Measurement("shoulderWidth", 340), - Measurement("shoulderHeight", 1240) + val userBodyProfile = + UserBodyProfile( + "female", + 32, + 1630, + "50.00", + mutableSetOf( + Measurement("hip", 830), + Measurement("hip", 830), + Measurement("bust", 755), + Measurement("neck", 300), + Measurement("rise", 215), + Measurement("bicep", 220), + Measurement("thigh", 480), + Measurement("waist", 630), + Measurement("inseam", 700), + Measurement("sleeve", 720), + Measurement("shoulder", 370), + Measurement("hipWidth", 300), + Measurement("bustWidth", 245), + Measurement("hipHeight", 750), + Measurement("headHeight", 215), + Measurement("kneeHeight", 395), + Measurement("waistWidth", 225), + Measurement("waistHeight", 920), + Measurement("armpitHeight", 1130), + Measurement("sleeveLength", 520), + Measurement("shoulderWidth", 340), + Measurement("shoulderHeight", 1240), + ), ) - ) } diff --git a/virtusize/src/test/java/com/virtusize/android/network/MockHttpsURLConnection.kt b/virtusize/src/test/java/com/virtusize/android/network/MockHttpsURLConnection.kt index a5c05228..50352b0e 100644 --- a/virtusize/src/test/java/com/virtusize/android/network/MockHttpsURLConnection.kt +++ b/virtusize/src/test/java/com/virtusize/android/network/MockHttpsURLConnection.kt @@ -9,49 +9,48 @@ import javax.net.ssl.HttpsURLConnection class MockHttpsURLConnection constructor(url: URL, private val mockedResponse: MockedResponse) : HttpsURLConnection(url) { + override fun getInputStream(): InputStream? = mockedResponse.response - override fun getInputStream(): InputStream? = mockedResponse.response + override fun getErrorStream(): InputStream? = mockedResponse.response - override fun getErrorStream(): InputStream? = mockedResponse.response + override fun getCipherSuite(): String { + return "" + } - override fun getCipherSuite(): String { - return "" - } + override fun getLocalCertificates(): Array { + return arrayOf() + } - override fun getLocalCertificates(): Array { - return arrayOf() - } + override fun getServerCertificates(): Array { + return arrayOf() + } - override fun getServerCertificates(): Array { - return arrayOf() - } + override fun getHeaderField(name: String?): String? = name?.let { mockedResponse.headers[it] } - override fun getHeaderField(name: String?): String? = name?.let { mockedResponse.headers[it] } + override fun getResponseMessage(): String? { + return mockedResponse.message + } - override fun getResponseMessage(): String? { - return mockedResponse.message - } + override fun getOutputStream(): OutputStream { + return ByteArrayOutputStream() + } - override fun getOutputStream(): OutputStream { - return ByteArrayOutputStream() - } - - override fun getResponseCode(): Int { - return mockedResponse.code - } + override fun getResponseCode(): Int { + return mockedResponse.code + } - override fun connect() {} + override fun connect() {} - override fun disconnect() {} + override fun disconnect() {} - override fun usingProxy(): Boolean { - return false + override fun usingProxy(): Boolean { + return false + } } -} class MockedResponse( val code: Int, val response: InputStream?, val message: String? = null, - val headers: Map = emptyMap() + val headers: Map = emptyMap(), ) diff --git a/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceTest.kt b/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceTest.kt index 8c2efccc..a4bdd247 100644 --- a/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/network/VirtusizeAPIServiceTest.kt @@ -36,7 +36,6 @@ import java.net.URL @Config(sdk = [Build.VERSION_CODES.Q]) @ExperimentalCoroutinesApi class VirtusizeAPIServiceTest { - @get:Rule var mainCoroutineRule = MainCoroutineRule() @@ -60,68 +59,76 @@ class VirtusizeAPIServiceTest { } @Test - fun testProductDataCheck_isValidProduct_hasExpectedData() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse(200, TestFixtures.PRODUCT_DATA_CHECK.toString().byteInputStream()) + fun testProductDataCheck_isValidProduct_hasExpectedData() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse( + 200, + TestFixtures.PRODUCT_DATA_CHECK.toString().byteInputStream(), + ), + ), ) - ) - - val actualProductCheck = - virtusizeAPIService.productDataCheck(TestFixtures.VIRTUSIZE_PRODUCT).successData - - assertThat(actualProductCheck?.name).isEqualTo("backend-checked-product") - assertThat(actualProductCheck?.data?.productTypeName).isEqualTo("pants") - assertThat(actualProductCheck?.data?.storeName).isEqualTo("virtusize") - assertThat(actualProductCheck?.data?.storeId).isEqualTo(2) - assertThat(actualProductCheck?.data?.validProduct).isTrue() - assertThat(actualProductCheck?.data?.fetchMetaData).isFalse() - assertThat(actualProductCheck?.data?.productDataId).isEqualTo(7110384) - assertThat(actualProductCheck?.data?.productTypeId).isEqualTo(5) - assertThat(actualProductCheck?.data?.shouldSeePhTooltip).isTrue() - assertThat(actualProductCheck?.productId).isEqualTo("694") - } + + val actualProductCheck = + virtusizeAPIService.productDataCheck(TestFixtures.VIRTUSIZE_PRODUCT).successData + + assertThat(actualProductCheck?.name).isEqualTo("backend-checked-product") + assertThat(actualProductCheck?.data?.productTypeName).isEqualTo("pants") + assertThat(actualProductCheck?.data?.storeName).isEqualTo("virtusize") + assertThat(actualProductCheck?.data?.storeId).isEqualTo(2) + assertThat(actualProductCheck?.data?.validProduct).isTrue() + assertThat(actualProductCheck?.data?.fetchMetaData).isFalse() + assertThat(actualProductCheck?.data?.productDataId).isEqualTo(7110384) + assertThat(actualProductCheck?.data?.productTypeId).isEqualTo(5) + assertThat(actualProductCheck?.data?.shouldSeePhTooltip).isTrue() + assertThat(actualProductCheck?.productId).isEqualTo("694") + } @Test - fun testProductDataCheck_isInvalidProduct() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse( - 200, - TestFixtures.INVALID_PRODUCT_DATA_CHECK.toString().byteInputStream() - ) + fun testProductDataCheck_isInvalidProduct() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse( + 200, + TestFixtures.INVALID_PRODUCT_DATA_CHECK.toString().byteInputStream(), + ), + ), ) - ) - val actualProductCheck = - virtusizeAPIService.productDataCheck(TestFixtures.VIRTUSIZE_PRODUCT).successData + val actualProductCheck = + virtusizeAPIService.productDataCheck(TestFixtures.VIRTUSIZE_PRODUCT).successData - assertThat(actualProductCheck?.name).isEqualTo("backend-checked-product") - assertThat(actualProductCheck?.data?.validProduct).isFalse() - assertThat(actualProductCheck?.data?.productDataId).isEqualTo(0) - } + assertThat(actualProductCheck?.name).isEqualTo("backend-checked-product") + assertThat(actualProductCheck?.data?.validProduct).isFalse() + assertThat(actualProductCheck?.data?.productDataId).isEqualTo(0) + } @Test - fun testProductDataCheck_provideWrongAPIKey_hasNetworkError() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse( - HttpURLConnection.HTTP_FORBIDDEN, - "{ \"Code\": \"ForbiddenError\", \"Message\": \"ForbiddenError: \" }" - .byteInputStream() - ) + fun testProductDataCheck_provideWrongAPIKey_hasNetworkError() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse( + HttpURLConnection.HTTP_FORBIDDEN, + "{ \"Code\": \"ForbiddenError\", \"Message\": \"ForbiddenError: \" }" + .byteInputStream(), + ), + ), ) - ) - val actualError = - virtusizeAPIService.productDataCheck(TestFixtures.VIRTUSIZE_PRODUCT).failureData + val actualError = + virtusizeAPIService.productDataCheck(TestFixtures.VIRTUSIZE_PRODUCT).failureData - assertThat(actualError?.code).isEqualTo(HttpURLConnection.HTTP_FORBIDDEN) - assertThat(actualError?.message).isEqualTo(VirtusizeErrorType.ApiKeyNullOrInvalid.message()) - assertThat(actualError?.type).isEqualTo(VirtusizeErrorType.ApiKeyNullOrInvalid) - } + assertThat(actualError?.code).isEqualTo(HttpURLConnection.HTTP_FORBIDDEN) + assertThat( + actualError?.message, + ).isEqualTo(VirtusizeErrorType.ApiKeyNullOrInvalid.message()) + assertThat(actualError?.type).isEqualTo(VirtusizeErrorType.ApiKeyNullOrInvalid) + } @Test fun testSendProductImageToBackend_whenSuccessful_hasExpectedProductMetaDataHints() = @@ -131,140 +138,146 @@ class VirtusizeAPIServiceTest { mockURL, MockedResponse( 200, - TestFixtures.PRODUCT_META_DATA_HINTS.toString().byteInputStream() - ) - ) + TestFixtures.PRODUCT_META_DATA_HINTS.toString().byteInputStream(), + ), + ), ) val actualProductMetaDataHints = virtusizeAPIService.sendProductImageToBackend( - TestFixtures.VIRTUSIZE_PRODUCT + TestFixtures.VIRTUSIZE_PRODUCT, ).successData assertThat(actualProductMetaDataHints?.apiKey).isEqualTo(TestFixtures.API_KEY) assertThat( - actualProductMetaDataHints?.imageUrl + actualProductMetaDataHints?.imageUrl, ).isEqualTo( - "http://www.test.com/goods/31/12/11/71/1234_COL_COL02_570.jpg" + "http://www.test.com/goods/31/12/11/71/1234_COL_COL02_570.jpg", ) assertThat( - actualProductMetaDataHints?.cloudinaryPublicId + actualProductMetaDataHints?.cloudinaryPublicId, ).isEqualTo( - "test_cloudinaryPublicId" + "test_cloudinaryPublicId", ) assertThat(actualProductMetaDataHints?.externalProductId).isEqualTo("694") } @Test - fun testSendProductImageToBackend_whenFailed_hasExpectedErrorInfo() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse( - 500, - INTERNAL_SERVER_ERROR_RESPONSE.byteInputStream(), - null - ) + fun testSendProductImageToBackend_whenFailed_hasExpectedErrorInfo() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse( + 500, + INTERNAL_SERVER_ERROR_RESPONSE.byteInputStream(), + null, + ), + ), ) - ) - val actualError = - virtusizeAPIService.sendProductImageToBackend( - TestFixtures.VIRTUSIZE_PRODUCT - ).failureData - - assertThat(actualError?.code).isEqualTo(500) - assertThat(actualError?.message).contains(INTERNAL_SERVER_ERROR_RESPONSE) - assertThat(actualError?.type).isEqualTo(VirtusizeErrorType.APIError) - } + val actualError = + virtusizeAPIService.sendProductImageToBackend( + TestFixtures.VIRTUSIZE_PRODUCT, + ).failureData + + assertThat(actualError?.code).isEqualTo(500) + assertThat(actualError?.message).contains(INTERNAL_SERVER_ERROR_RESPONSE) + assertThat(actualError?.type).isEqualTo(VirtusizeErrorType.APIError) + } @Test - fun testSendUserSawProductEvent_whenSuccessful_onSuccessShouldBeCalled() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse( - 201, - TestFixtures.USER_SAW_PRODUCT_EVENT_RESPONSE.toString().byteInputStream() - ) + fun testSendUserSawProductEvent_whenSuccessful_onSuccessShouldBeCalled() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse( + 201, + TestFixtures.USER_SAW_PRODUCT_EVENT_RESPONSE.toString().byteInputStream(), + ), + ), ) - ) - val sendEventResponse = - virtusizeAPIService.sendEvent(VirtusizeEvent("user-saw-product", null)) + val sendEventResponse = + virtusizeAPIService.sendEvent(VirtusizeEvent("user-saw-product", null)) - assertThat(sendEventResponse.isSuccessful).isTrue() - } + assertThat(sendEventResponse.isSuccessful).isTrue() + } @Test - fun testRetrieveStoreInfo_getsFullStoreInfoResponse_hasExpectedStore() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse( - 200, - TestFixtures.STORE_WITH_FULL_INFO.toString().byteInputStream() - ) + fun testRetrieveStoreInfo_getsFullStoreInfoResponse_hasExpectedStore() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse( + 200, + TestFixtures.STORE_WITH_FULL_INFO.toString().byteInputStream(), + ), + ), ) - ) - - val actualStore = virtusizeAPIService.getStoreInfo().successData - - assertThat(actualStore?.id).isEqualTo(2) - assertThat(actualStore?.surveyLink).isEqualTo("https://www.survey.com/s/xxxxxx") - assertThat(actualStore?.name).isEqualTo("Virtusize") - assertThat(actualStore?.shortName).isEqualTo("virtusize") - assertThat(actualStore?.lengthUnitId).isEqualTo(2) - assertThat(actualStore?.apiKey).isEqualTo(TestFixtures.API_KEY) - assertThat(actualStore?.created).isEqualTo("2011-01-01T00:00:00Z") - assertThat(actualStore?.updated).isEqualTo("2020-04-20T02:33:58Z") - assertThat(actualStore?.disabled).isEqualTo("2018-05-29 04:32:45") - assertThat(actualStore?.typeMapperEnabled).isEqualTo(false) - assertThat(actualStore?.region).isEqualTo("KR") - } + + val actualStore = virtusizeAPIService.getStoreInfo().successData + + assertThat(actualStore?.id).isEqualTo(2) + assertThat(actualStore?.surveyLink).isEqualTo("https://www.survey.com/s/xxxxxx") + assertThat(actualStore?.name).isEqualTo("Virtusize") + assertThat(actualStore?.shortName).isEqualTo("virtusize") + assertThat(actualStore?.lengthUnitId).isEqualTo(2) + assertThat(actualStore?.apiKey).isEqualTo(TestFixtures.API_KEY) + assertThat(actualStore?.created).isEqualTo("2011-01-01T00:00:00Z") + assertThat(actualStore?.updated).isEqualTo("2020-04-20T02:33:58Z") + assertThat(actualStore?.disabled).isEqualTo("2018-05-29 04:32:45") + assertThat(actualStore?.typeMapperEnabled).isEqualTo(false) + assertThat(actualStore?.region).isEqualTo("KR") + } @Test - fun testRetrieveStoreInfo_getsSomeNullValuesForStoreInfo_hasExpectedStore() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse( - 200, - TestFixtures.STORE_WITH_NULL_VALUES.toString().byteInputStream() - ) + fun testRetrieveStoreInfo_getsSomeNullValuesForStoreInfo_hasExpectedStore() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse( + 200, + TestFixtures.STORE_WITH_NULL_VALUES.toString().byteInputStream(), + ), + ), ) - ) - - val actualStore = virtusizeAPIService.getStoreInfo().successData - - assertThat(actualStore?.id).isEqualTo(2) - assertThat(actualStore?.surveyLink).isEqualTo("https://www.survey.com/s/xxxxxx") - assertThat(actualStore?.name).isEqualTo("Virtusize") - assertThat(actualStore?.shortName).isEqualTo("virtusize") - assertThat(actualStore?.lengthUnitId).isEqualTo(2) - assertThat(actualStore?.apiKey).isEqualTo(TestFixtures.API_KEY) - assertThat(actualStore?.created).isEqualTo("2011-01-01T00:00:00Z") - assertThat(actualStore?.updated).isEqualTo("2020-04-20T02:33:58Z") - assertThat(actualStore?.disabled).isEqualTo("") - assertThat(actualStore?.typeMapperEnabled).isEqualTo(false) - assertThat(actualStore?.region).isEqualTo("JP") - } + + val actualStore = virtusizeAPIService.getStoreInfo().successData + + assertThat(actualStore?.id).isEqualTo(2) + assertThat(actualStore?.surveyLink).isEqualTo("https://www.survey.com/s/xxxxxx") + assertThat(actualStore?.name).isEqualTo("Virtusize") + assertThat(actualStore?.shortName).isEqualTo("virtusize") + assertThat(actualStore?.lengthUnitId).isEqualTo(2) + assertThat(actualStore?.apiKey).isEqualTo(TestFixtures.API_KEY) + assertThat(actualStore?.created).isEqualTo("2011-01-01T00:00:00Z") + assertThat(actualStore?.updated).isEqualTo("2020-04-20T02:33:58Z") + assertThat(actualStore?.disabled).isEqualTo("") + assertThat(actualStore?.typeMapperEnabled).isEqualTo(false) + assertThat(actualStore?.region).isEqualTo("JP") + } @Test - fun testSendOrder_whenSuccessful_onSuccessShouldBeCalled() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse(204, "".byteInputStream()) + fun testSendOrder_whenSuccessful_onSuccessShouldBeCalled() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse(204, "".byteInputStream()), + ), ) - ) - val sendOrderResponse = virtusizeAPIService.sendOrder( - "", - TestFixtures.VIRTUSIZE_ORDER - ) + val sendOrderResponse = + virtusizeAPIService.sendOrder( + "", + TestFixtures.VIRTUSIZE_ORDER, + ) - assertThat(sendOrderResponse.isSuccessful).isTrue() - } + assertThat(sendOrderResponse.isSuccessful).isTrue() + } @Test fun testGetStoreProductInfo_whenSuccessful_onSuccessShouldReturnExpectedStoreProduct() = @@ -274,9 +287,9 @@ class VirtusizeAPIServiceTest { mockURL, MockedResponse( 200, - ProductFixtures.STORE_PRODUCT_INFO_JSON_DATA.toString().byteInputStream() - ) - ) + ProductFixtures.STORE_PRODUCT_INFO_JSON_DATA.toString().byteInputStream(), + ), + ), ) val actualProduct = @@ -290,40 +303,41 @@ class VirtusizeAPIServiceTest { assertThat(actualProduct?.storeId).isEqualTo(TestFixtures.STORE_ID) assertThat(actualProduct?.storeProductMeta?.id).isEqualTo(1) assertThat(actualProduct?.storeProductMeta?.id).isEqualTo(1) - val expectedAdditionalInfo = StoreProductAdditionalInfo( - "Virtusize", - "female", - mutableSetOf( - ProductSize( - "38", - mutableSetOf( - Measurement("height", 760), - Measurement("bust", 660), - Measurement("sleeve", 845) - ) + val expectedAdditionalInfo = + StoreProductAdditionalInfo( + "Virtusize", + "female", + mutableSetOf( + ProductSize( + "38", + mutableSetOf( + Measurement("height", 760), + Measurement("bust", 660), + Measurement("sleeve", 845), + ), + ), + ProductSize( + "36", + mutableSetOf( + Measurement("height", 750), + Measurement("bust", 645), + Measurement("sleeve", 825), + ), + ), ), - ProductSize( - "36", - mutableSetOf( - Measurement("height", 750), - Measurement("bust", 645), - Measurement("sleeve", 825) - ) - ) - ), - mutableMapOf( - "hip" to 85, - "size" to "38", - "waist" to 56, - "bust" to 78, - "height" to 165 - ), - "regular", - "fashionable", - BrandSizing("large", false) - ) + mutableMapOf( + "hip" to 85, + "size" to "38", + "waist" to 56, + "bust" to 78, + "height" to 165, + ), + "regular", + "fashionable", + BrandSizing("large", false), + ) assertThat(actualProduct?.storeProductMeta?.additionalInfo).isEqualTo( - expectedAdditionalInfo + expectedAdditionalInfo, ) } @@ -335,9 +349,9 @@ class VirtusizeAPIServiceTest { mockURL, MockedResponse( 200, - ProductFixtures.PRODUCT_TYPE_JSON_ARRAY.toString().byteInputStream() - ) - ) + ProductFixtures.PRODUCT_TYPE_JSON_ARRAY.toString().byteInputStream(), + ), + ), ) val actualProductTypeList = virtusizeAPIService.getProductTypes().successData @@ -350,10 +364,10 @@ class VirtusizeAPIServiceTest { mutableSetOf( Weight("bust", 1f), Weight("waist", 1f), - Weight("height", 0.25f) + Weight("height", 0.25f), ), - mutableListOf(1, 16) - ) + mutableListOf(1, 16), + ), ) assertThat(actualProductTypeList?.get(3)).isEqualTo( ProductType( @@ -362,29 +376,31 @@ class VirtusizeAPIServiceTest { mutableSetOf( Weight("depth", 1f), Weight("width", 2f), - Weight("height", 1f) + Weight("height", 1f), ), - mutableListOf(18, 19, 25, 26) - ) + mutableListOf(18, 19, 25, 26), + ), ) } @Test - fun testGetDeleteUserResponse() = runBlocking { - val expectedDeleteUserJsonResponse = "{" + - "\"wardrobe(s) deleted from DB\":1," + - "\"user product(s) deleted from DB\":1,\"duration\":0.04" + - "}" - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse(200, expectedDeleteUserJsonResponse.byteInputStream()) + fun testGetDeleteUserResponse() = + runBlocking { + val expectedDeleteUserJsonResponse = + "{" + + "\"wardrobe(s) deleted from DB\":1," + + "\"user product(s) deleted from DB\":1,\"duration\":0.04" + + "}" + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse(200, expectedDeleteUserJsonResponse.byteInputStream()), + ), ) - ) - val actualSuccessfulResponse = virtusizeAPIService.deleteUser().successData - assertThat(actualSuccessfulResponse).isNull() - } + val actualSuccessfulResponse = virtusizeAPIService.deleteUser().successData + assertThat(actualSuccessfulResponse).isNull() + } @Test fun testGetUserProductsResponse_userHasItemsInTheWardrobe_shouldReturnExpectedUserProducts() = @@ -394,9 +410,9 @@ class VirtusizeAPIServiceTest { mockURL, MockedResponse( 200, - ProductFixtures.USER_PRODUCT_JSON_ARRAY.toString().byteInputStream() - ) - ) + ProductFixtures.USER_PRODUCT_JSON_ARRAY.toString().byteInputStream(), + ), + ), ) val actualUserProductList = virtusizeAPIService.getUserProducts().successData @@ -411,8 +427,8 @@ class VirtusizeAPIServiceTest { mutableSetOf( Measurement("height", 820), Measurement("bust", 520), - Measurement("sleeve", 930) - ) + Measurement("sleeve", 930), + ), ) assertThat(actualUserProductList?.get(1)?.productType).isEqualTo(2) assertThat(actualUserProductList?.get(1)?.name).isEqualTo("test2") @@ -428,9 +444,9 @@ class VirtusizeAPIServiceTest { mockURL, MockedResponse( 200, - ProductFixtures.EMPTY_PRODUCT_JSON_ARRAY.toString().byteInputStream() - ) - ) + ProductFixtures.EMPTY_PRODUCT_JSON_ARRAY.toString().byteInputStream(), + ), + ), ) val actualUserProductList = virtusizeAPIService.getUserProducts().successData @@ -439,23 +455,24 @@ class VirtusizeAPIServiceTest { } @Test - fun testGetUserProductsResponse_wardrobeDoesNotExist_shouldReturn404Error() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse( - 404, - ProductFixtures.WARDROBE_NOT_FOUND_ERROR_JSONObject.toString().byteInputStream() - ) + fun testGetUserProductsResponse_wardrobeDoesNotExist_shouldReturn404Error() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse( + 404, + ProductFixtures.WARDROBE_NOT_FOUND_ERROR_JSONObject.toString().byteInputStream(), + ), + ), ) - ) - val actualError = virtusizeAPIService.getUserProducts().failureData + val actualError = virtusizeAPIService.getUserProducts().failureData - assertThat(actualError?.code).isEqualTo(HttpURLConnection.HTTP_NOT_FOUND) - assertThat(actualError?.message).contains("{\"detail\":\"No wardrobe found\"}") - assertThat(actualError?.type).isEqualTo(VirtusizeErrorType.APIError) - } + assertThat(actualError?.code).isEqualTo(HttpURLConnection.HTTP_NOT_FOUND) + assertThat(actualError?.message).contains("{\"detail\":\"No wardrobe found\"}") + assertThat(actualError?.type).isEqualTo(VirtusizeErrorType.APIError) + } @Test fun testGetUserBodyProfileResponse_userHasValidBodyProfile_shouldReturnExpectedBodyProfile() = @@ -465,9 +482,9 @@ class VirtusizeAPIServiceTest { mockURL, MockedResponse( 200, - TestFixtures.USER_BODY_JSONObject.toString().byteInputStream() - ) - ) + TestFixtures.USER_BODY_JSONObject.toString().byteInputStream(), + ), + ), ) val actualUserBodyProfile = virtusizeAPIService.getUserBodyProfile().successData @@ -499,71 +516,77 @@ class VirtusizeAPIServiceTest { Measurement("armpitHeight", 1130), Measurement("sleeveLength", 520), Measurement("shoulderWidth", 340), - Measurement("shoulderHeight", 1240) - ) + Measurement("shoulderHeight", 1240), + ), ) } @Test - fun testGetUserBodyProfileResponse_userHasEmptyBodyProfile_shouldExpectNull() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse( - 200, - TestFixtures.NULL_USER_BODY_PROFILE.toString().byteInputStream() - ) + fun testGetUserBodyProfileResponse_userHasEmptyBodyProfile_shouldExpectNull() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse( + 200, + TestFixtures.NULL_USER_BODY_PROFILE.toString().byteInputStream(), + ), + ), ) - ) - val actualUserBodyProfile = virtusizeAPIService.getUserBodyProfile().successData + val actualUserBodyProfile = virtusizeAPIService.getUserBodyProfile().successData - assertThat(actualUserBodyProfile).isNull() - } + assertThat(actualUserBodyProfile).isNull() + } @Test - fun testGetUserBodyProfileResponse_wardrobeDoesNotExist_shouldReturn404Error() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse( - 404, - ProductFixtures.WARDROBE_NOT_FOUND_ERROR_JSONObject.toString().byteInputStream() - ) + fun testGetUserBodyProfileResponse_wardrobeDoesNotExist_shouldReturn404Error() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse( + 404, + ProductFixtures.WARDROBE_NOT_FOUND_ERROR_JSONObject.toString().byteInputStream(), + ), + ), ) - ) - val actualError = virtusizeAPIService.getUserBodyProfile().failureData - assertThat(actualError?.code).isEqualTo(HttpURLConnection.HTTP_NOT_FOUND) - assertThat(actualError?.message).contains("{\"detail\":\"No wardrobe found\"}") - assertThat(actualError?.type).isEqualTo(VirtusizeErrorType.APIError) - } + val actualError = virtusizeAPIService.getUserBodyProfile().failureData + assertThat(actualError?.code).isEqualTo(HttpURLConnection.HTTP_NOT_FOUND) + assertThat(actualError?.message).contains("{\"detail\":\"No wardrobe found\"}") + assertThat(actualError?.type).isEqualTo(VirtusizeErrorType.APIError) + } @Test - fun testI18nResponse_whenSuccessful_shouldReturnExpectedI18Localization() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - mockURL, - MockedResponse( - 200, - TestUtils.readFileFromAssets("/i18n_en.json").toString().byteInputStream() - ) + fun testI18nResponse_whenSuccessful_shouldReturnExpectedI18Localization() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + mockURL, + MockedResponse( + 200, + TestUtils.readFileFromAssets("/i18n_en.json").toString().byteInputStream(), + ), + ), ) - ) - - val actualI18nLocalization = virtusizeAPIService.getI18n(VirtusizeLanguage.EN).successData - - assertThat( - actualI18nLocalization?.defaultNoDataText - ).isEqualTo( - "Find the right size before purchasing" - ) - assertThat( - actualI18nLocalization?.defaultAccessoryText - ).isEqualTo( - "See how everyday items fit" - ) - } + + val actualI18nLocalization = + virtusizeAPIService.getI18n( + VirtusizeLanguage.EN, + ).successData + + assertThat( + actualI18nLocalization?.defaultNoDataText, + ).isEqualTo( + "Find the right size before purchasing", + ) + assertThat( + actualI18nLocalization?.defaultAccessoryText, + ).isEqualTo( + "See how everyday items fit", + ) + } @Test fun testBodyRecommendedSize_whenSuccessful_shouldReturnExpectedBodyProfileRecommendedSize() = @@ -571,50 +594,52 @@ class VirtusizeAPIServiceTest { virtusizeAPIService.setHTTPURLConnection( MockHttpsURLConnection( mockURL, - MockedResponse(200, "{\"sizeName\": \"large\"}".byteInputStream()) - ) + MockedResponse(200, "{\"sizeName\": \"large\"}".byteInputStream()), + ), ) val actualBodyProfileRecommendedSize = virtusizeAPIService.getBodyProfileRecommendedSize( ProductFixtures.productTypes(), ProductFixtures.storeProduct(), - TestFixtures.userBodyProfile + TestFixtures.userBodyProfile, ).successData assertThat(actualBodyProfileRecommendedSize?.get(0)?.sizeName).isAnyOf("large", null) } @Test - fun testBodyRecommendedSize_whenStoreProductIsAnAccessory_shouldReturn400Error() = runBlocking { - virtusizeAPIService.setHTTPURLConnection( - MockHttpsURLConnection( - URL("https://size-recommendation.virtusize.jp/item"), - MockedResponse( - 400, - "{\"Code\": \"BadRequestError\", \"Message\": \"BadRequestError: \"}" - .byteInputStream() + fun testBodyRecommendedSize_whenStoreProductIsAnAccessory_shouldReturn400Error() = + runBlocking { + virtusizeAPIService.setHTTPURLConnection( + MockHttpsURLConnection( + URL("https://size-recommendation.virtusize.jp/item"), + MockedResponse( + 400, + "{\"Code\": \"BadRequestError\", \"Message\": \"BadRequestError: \"}" + .byteInputStream(), + ), + ), + ) + + val actualApiResponse = + virtusizeAPIService.getBodyProfileRecommendedSize( + ProductFixtures.productTypes(), + ProductFixtures.storeProduct(18), + TestFixtures.userBodyProfile, ) + + val actualError = (actualApiResponse as? VirtusizeApiResponse.Error)?.error + + assertThat(actualError?.code).isEqualTo(HttpURLConnection.HTTP_BAD_REQUEST) + assertThat( + actualError?.message, + ).contains( + "/item" + + " - {\"Code\": \"BadRequestError\", \"Message\": \"BadRequestError: \"}", ) - ) - - val actualApiResponse = virtusizeAPIService.getBodyProfileRecommendedSize( - ProductFixtures.productTypes(), - ProductFixtures.storeProduct(18), - TestFixtures.userBodyProfile - ) - - val actualError = (actualApiResponse as? VirtusizeApiResponse.Error)?.error - - assertThat(actualError?.code).isEqualTo(HttpURLConnection.HTTP_BAD_REQUEST) - assertThat( - actualError?.message - ).contains( - "/item" + - " - {\"Code\": \"BadRequestError\", \"Message\": \"BadRequestError: \"}" - ) - assertThat(actualError?.type).isEqualTo(VirtusizeErrorType.APIError) - } + assertThat(actualError?.type).isEqualTo(VirtusizeErrorType.APIError) + } companion object { private const val INTERNAL_SERVER_ERROR_RESPONSE = diff --git a/virtusize/src/test/java/com/virtusize/android/data/parsers/BrandSizingJsonParserTest.kt b/virtusize/src/test/java/com/virtusize/android/parsers/BrandSizingJsonParserTest.kt similarity index 77% rename from virtusize/src/test/java/com/virtusize/android/data/parsers/BrandSizingJsonParserTest.kt rename to virtusize/src/test/java/com/virtusize/android/parsers/BrandSizingJsonParserTest.kt index c3fd06c1..c65fb2c2 100644 --- a/virtusize/src/test/java/com/virtusize/android/data/parsers/BrandSizingJsonParserTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/parsers/BrandSizingJsonParserTest.kt @@ -7,15 +7,15 @@ import org.json.JSONObject import org.junit.Test class BrandSizingJsonParserTest { - @Test fun parse_validJsonData_shouldReturnExpectedObject() { val actualBrandSizing = BrandSizingJsonParser().parse(BRAND_SIZING_JSON_DATA) - val expectedBrandSizing = BrandSizing( - "small", - false - ) + val expectedBrandSizing = + BrandSizing( + "small", + false, + ) Truth.assertThat(actualBrandSizing).isEqualTo(expectedBrandSizing) } @@ -29,13 +29,14 @@ class BrandSizingJsonParserTest { } companion object { - private val BRAND_SIZING_JSON_DATA = JSONObject( - """ + private val BRAND_SIZING_JSON_DATA = + JSONObject( + """ { "compare":"small", "itemBrand":false } - """.trimIndent() - ) + """.trimIndent(), + ) } } diff --git a/virtusize/src/test/java/com/virtusize/android/data/parsers/I18nLocalizationJsonParserTest.kt b/virtusize/src/test/java/com/virtusize/android/parsers/I18nLocalizationJsonParserTest.kt similarity index 78% rename from virtusize/src/test/java/com/virtusize/android/data/parsers/I18nLocalizationJsonParserTest.kt rename to virtusize/src/test/java/com/virtusize/android/parsers/I18nLocalizationJsonParserTest.kt index 10e68e4c..52da22be 100644 --- a/virtusize/src/test/java/com/virtusize/android/data/parsers/I18nLocalizationJsonParserTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/parsers/I18nLocalizationJsonParserTest.kt @@ -19,14 +19,13 @@ import java.util.Locale @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.Q]) class I18nLocalizationJsonParserTest { - private val context: Context = ApplicationProvider.getApplicationContext() @Test fun parseI18N_englishLocalization_shouldReturnExpectedObject() { val actualI18nLocalization = I18nLocalizationJsonParser(context, VirtusizeLanguage.EN).parse( - TestUtils.readFileFromAssets("/i18n_en.json") + TestUtils.readFileFromAssets("/i18n_en.json"), ) val expectedI18nLocalization = getExpectedI18nLocalization(context) @@ -38,34 +37,36 @@ class I18nLocalizationJsonParserTest { fun parseI18N_emptyJsonData_shouldReturnExpectedObject() { val actualI18nLocalization = I18nLocalizationJsonParser(context, VirtusizeLanguage.EN).parse( - TestFixtures.EMPTY_JSON_DATA + TestFixtures.EMPTY_JSON_DATA, ) - val expectedI18nLocalization = I18nLocalization( - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "", - "" - ) + val expectedI18nLocalization = + I18nLocalization( + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + ) assertThat(actualI18nLocalization).isEqualTo(expectedI18nLocalization) } @Test fun parseI18NJP_japaneseLocalization_shouldReturnExpectedObject() { - val actualI18nLocalization = I18nLocalizationJsonParser( - context, - VirtusizeLanguage.JP - ).parse(TestUtils.readFileFromAssets("/i18n_jp.json")) + val actualI18nLocalization = + I18nLocalizationJsonParser( + context, + VirtusizeLanguage.JP, + ).parse(TestUtils.readFileFromAssets("/i18n_jp.json")) var conf: Configuration = context.resources.configuration conf = Configuration(conf) @@ -78,10 +79,11 @@ class I18nLocalizationJsonParserTest { @Test fun parseI18NKO_koreanLocalization_shouldReturnExpectedObject() { - val actualI18nLocalization = I18nLocalizationJsonParser( - context, - VirtusizeLanguage.KR - ).parse(TestUtils.readFileFromAssets("/i18n_ko.json")) + val actualI18nLocalization = + I18nLocalizationJsonParser( + context, + VirtusizeLanguage.KR, + ).parse(TestUtils.readFileFromAssets("/i18n_ko.json")) var conf: Configuration = context.resources.configuration conf = Configuration(conf) @@ -95,44 +97,44 @@ class I18nLocalizationJsonParserTest { private fun getExpectedI18nLocalization(localizedContext: Context): I18nLocalization { return I18nLocalization( localizedContext.getString( - com.virtusize.android.core.R.string.inpage_default_accessory_text + com.virtusize.android.core.R.string.inpage_default_accessory_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_has_product_top_text + com.virtusize.android.core.R.string.inpage_has_product_top_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_has_product_bottom_text + com.virtusize.android.core.R.string.inpage_has_product_bottom_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_one_size_close_top_text + com.virtusize.android.core.R.string.inpage_one_size_close_top_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_one_size_smaller_top_text + com.virtusize.android.core.R.string.inpage_one_size_smaller_top_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_one_size_larger_top_text + com.virtusize.android.core.R.string.inpage_one_size_larger_top_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_one_size_close_bottom_text + com.virtusize.android.core.R.string.inpage_one_size_close_bottom_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_one_size_smaller_bottom_text + com.virtusize.android.core.R.string.inpage_one_size_smaller_bottom_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_one_size_larger_bottom_text + com.virtusize.android.core.R.string.inpage_one_size_larger_bottom_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_one_size_body_profile_text + com.virtusize.android.core.R.string.inpage_one_size_body_profile_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_multi_size_comparison_text + com.virtusize.android.core.R.string.inpage_multi_size_comparison_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_multi_size_body_profile_text + com.virtusize.android.core.R.string.inpage_multi_size_body_profile_text, ), localizedContext.getString( - com.virtusize.android.core.R.string.inpage_no_data_text - ) + com.virtusize.android.core.R.string.inpage_no_data_text, + ), ) } } diff --git a/virtusize/src/test/java/com/virtusize/android/data/parsers/ProductMetaDataHintsJsonParserTest.kt b/virtusize/src/test/java/com/virtusize/android/parsers/ProductMetaDataHintsJsonParserTest.kt similarity index 67% rename from virtusize/src/test/java/com/virtusize/android/data/parsers/ProductMetaDataHintsJsonParserTest.kt rename to virtusize/src/test/java/com/virtusize/android/parsers/ProductMetaDataHintsJsonParserTest.kt index 9796bf9d..cb680ca5 100644 --- a/virtusize/src/test/java/com/virtusize/android/data/parsers/ProductMetaDataHintsJsonParserTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/parsers/ProductMetaDataHintsJsonParserTest.kt @@ -6,18 +6,18 @@ import com.virtusize.android.fixtures.TestFixtures import org.junit.Test class ProductMetaDataHintsJsonParserTest { - @Test fun parse_shouldReturnExpectedObject() { val actualProductMetaDataHints = ProductMetaDataHintsJsonParser().parse(TestFixtures.PRODUCT_META_DATA_HINTS) - val expectedProductMetaDataHints = ProductMetaDataHints( - "test_apiKey", - "http://www.test.com/goods/31/12/11/71/1234_COL_COL02_570.jpg", - "test_cloudinaryPublicId", - "694" - ) + val expectedProductMetaDataHints = + ProductMetaDataHints( + "test_apiKey", + "http://www.test.com/goods/31/12/11/71/1234_COL_COL02_570.jpg", + "test_cloudinaryPublicId", + "694", + ) assertThat(actualProductMetaDataHints).isEqualTo(expectedProductMetaDataHints) } diff --git a/virtusize/src/test/java/com/virtusize/android/data/parsers/ProductSizeJsonParserTest.kt b/virtusize/src/test/java/com/virtusize/android/parsers/ProductSizeJsonParserTest.kt similarity index 68% rename from virtusize/src/test/java/com/virtusize/android/data/parsers/ProductSizeJsonParserTest.kt rename to virtusize/src/test/java/com/virtusize/android/parsers/ProductSizeJsonParserTest.kt index 24f113aa..78cbf7b8 100644 --- a/virtusize/src/test/java/com/virtusize/android/data/parsers/ProductSizeJsonParserTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/parsers/ProductSizeJsonParserTest.kt @@ -8,19 +8,19 @@ import org.json.JSONObject import org.junit.Test class ProductSizeJsonParserTest { - @Test fun parse_validJsonData_shouldReturnExpectedObject() { val actualProductSize = ProductSizeJsonParser().parse(PRODUCT_SIZE_JSON_DATA) - val expectedProductSize = ProductSize( - " ", - mutableSetOf( - Measurement("width", 150), - Measurement("depth", 100), - Measurement("height", 160) + val expectedProductSize = + ProductSize( + " ", + mutableSetOf( + Measurement("width", 150), + Measurement("depth", 100), + Measurement("height", 160), + ), ) - ) assertThat(actualProductSize).isEqualTo(expectedProductSize) } @@ -29,14 +29,15 @@ class ProductSizeJsonParserTest { fun parse_withNullMeasurementData_shouldReturnExpectedObject() { val actualProductSize = ProductSizeJsonParser().parse(PRODUCT_SIZE_WITH_NULL_INFO) - val expectedProductSize = ProductSize( - "", - mutableSetOf( - Measurement("height", 560), - Measurement("bust", 450), - Measurement("sleeve", 730) + val expectedProductSize = + ProductSize( + "", + mutableSetOf( + Measurement("height", 560), + Measurement("bust", 450), + Measurement("sleeve", 730), + ), ) - ) assertThat(actualProductSize).isEqualTo(expectedProductSize) } @@ -49,8 +50,9 @@ class ProductSizeJsonParserTest { } companion object { - val PRODUCT_SIZE_JSON_DATA = JSONObject( - """ + val PRODUCT_SIZE_JSON_DATA = + JSONObject( + """ { "name": " ", "measurements": { @@ -59,11 +61,12 @@ class ProductSizeJsonParserTest { "height": 160 } } - """.trimIndent() - ) + """.trimIndent(), + ) - val PRODUCT_SIZE_WITH_NULL_INFO = JSONObject( - """ + val PRODUCT_SIZE_WITH_NULL_INFO = + JSONObject( + """ { "name": "", "measurements": { @@ -76,7 +79,7 @@ class ProductSizeJsonParserTest { "bicep": null } } - """.trimIndent() - ) + """.trimIndent(), + ) } } diff --git a/virtusize/src/test/java/com/virtusize/android/parsers/StoreJsonParserTest.kt b/virtusize/src/test/java/com/virtusize/android/parsers/StoreJsonParserTest.kt new file mode 100644 index 00000000..d9cdb15c --- /dev/null +++ b/virtusize/src/test/java/com/virtusize/android/parsers/StoreJsonParserTest.kt @@ -0,0 +1,52 @@ +package com.virtusize.android.data.parsers + +import com.google.common.truth.Truth.assertThat +import com.virtusize.android.data.remote.Store +import com.virtusize.android.fixtures.TestFixtures +import org.junit.Test + +class StoreJsonParserTest { + @Test + fun parse_hasFullInfo_shouldReturnExpectedObject() { + val actualStore = StoreJsonParser().parse(TestFixtures.STORE_WITH_FULL_INFO) + + val expectedStore = + Store( + 2, + "https://www.survey.com/s/xxxxxx", + "Virtusize", + "virtusize", + 2, + "test_apiKey", + "2011-01-01T00:00:00Z", + "2020-04-20T02:33:58Z", + "2018-05-29 04:32:45", + false, + "KR", + ) + + assertThat(actualStore).isEqualTo(expectedStore) + } + + @Test + fun parse_hasNullValues_shouldReturnExpectedObject() { + val actualStore = StoreJsonParser().parse(TestFixtures.STORE_WITH_NULL_VALUES) + + val expectedStore = + Store( + 2, + "https://www.survey.com/s/xxxxxx", + "Virtusize", + "virtusize", + 2, + "test_apiKey", + "2011-01-01T00:00:00Z", + "2020-04-20T02:33:58Z", + "", + false, + "JP", + ) + + assertThat(actualStore).isEqualTo(expectedStore) + } +} diff --git a/virtusize/src/test/java/com/virtusize/android/data/parsers/StoreProductAdditionalInfoJsonParserTest.kt b/virtusize/src/test/java/com/virtusize/android/parsers/StoreProductAdditionalInfoJsonParserTest.kt similarity index 83% rename from virtusize/src/test/java/com/virtusize/android/data/parsers/StoreProductAdditionalInfoJsonParserTest.kt rename to virtusize/src/test/java/com/virtusize/android/parsers/StoreProductAdditionalInfoJsonParserTest.kt index b6520ae6..008234f8 100644 --- a/virtusize/src/test/java/com/virtusize/android/data/parsers/StoreProductAdditionalInfoJsonParserTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/parsers/StoreProductAdditionalInfoJsonParserTest.kt @@ -7,12 +7,12 @@ import org.json.JSONObject import org.junit.Test class StoreProductAdditionalInfoJsonParserTest { - @Test fun parse_validJsonData_shouldReturnExpectedStoreProductAdditionalInfo() { - val actualAdditionalInfo = StoreProductAdditionalInfoJsonParser().parse( - ADDITIONAL_INFO_JSON_DATA - ) + val actualAdditionalInfo = + StoreProductAdditionalInfoJsonParser().parse( + ADDITIONAL_INFO_JSON_DATA, + ) assertThat(actualAdditionalInfo?.brand).isEqualTo("Virtusize") assertThat(actualAdditionalInfo?.sizes?.size).isEqualTo(0) @@ -22,8 +22,8 @@ class StoreProductAdditionalInfoJsonParserTest { assertThat(actualAdditionalInfo?.brandSizing).isEqualTo( BrandSizing( "true", - true - ) + true, + ), ) } @@ -35,8 +35,9 @@ class StoreProductAdditionalInfoJsonParserTest { } companion object { - private val ADDITIONAL_INFO_JSON_DATA = JSONObject( - """ + private val ADDITIONAL_INFO_JSON_DATA = + JSONObject( + """ { "brand":"Virtusize", "gender":"null", @@ -50,7 +51,7 @@ class StoreProductAdditionalInfoJsonParserTest { "itemBrand":true } } - """.trimIndent() - ) + """.trimIndent(), + ) } } diff --git a/virtusize/src/test/java/com/virtusize/android/data/parsers/StoreProductJsonParserTest.kt b/virtusize/src/test/java/com/virtusize/android/parsers/StoreProductJsonParserTest.kt similarity index 86% rename from virtusize/src/test/java/com/virtusize/android/data/parsers/StoreProductJsonParserTest.kt rename to virtusize/src/test/java/com/virtusize/android/parsers/StoreProductJsonParserTest.kt index 8714ca25..91784c4d 100644 --- a/virtusize/src/test/java/com/virtusize/android/data/parsers/StoreProductJsonParserTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/parsers/StoreProductJsonParserTest.kt @@ -9,7 +9,6 @@ import com.virtusize.android.fixtures.TestFixtures import org.junit.Test class StoreProductJsonParserTest { - @Test fun parse_validJsonData_shouldReturnExpectedStoreProduct() { val actualStoreProduct = @@ -23,18 +22,18 @@ class StoreProductJsonParserTest { mutableSetOf( Measurement("height", 760), Measurement("bust", 660), - Measurement("sleeve", 845) - ) + Measurement("sleeve", 845), + ), ), ProductSize( "36", mutableSetOf( Measurement("height", 750), Measurement("bust", 645), - Measurement("sleeve", 825) - ) - ) - ) + Measurement("sleeve", 825), + ), + ), + ), ) assertThat(actualStoreProduct?.externalId).isEqualTo("694") assertThat(actualStoreProduct?.productType).isEqualTo(8) @@ -45,12 +44,12 @@ class StoreProductJsonParserTest { assertThat(actualStoreProduct?.storeProductMeta?.id).isEqualTo(1) assertThat(actualStoreProduct?.storeProductMeta?.brand).isEqualTo("Virtusize") assertThat( - actualStoreProduct?.storeProductMeta?.additionalInfo?.brand + actualStoreProduct?.storeProductMeta?.additionalInfo?.brand, ).isEqualTo( - "Virtusize" + "Virtusize", ) assertThat( - actualStoreProduct?.storeProductMeta?.additionalInfo?.sizes?.toMutableSet() + actualStoreProduct?.storeProductMeta?.additionalInfo?.sizes?.toMutableSet(), ).isEqualTo( mutableSetOf( ProductSize( @@ -58,18 +57,18 @@ class StoreProductJsonParserTest { mutableSetOf( Measurement("height", 760), Measurement("bust", 660), - Measurement("sleeve", 845) - ) + Measurement("sleeve", 845), + ), ), ProductSize( "36", mutableSetOf( Measurement("height", 750), Measurement("bust", 645), - Measurement("sleeve", 825) - ) - ) - ) + Measurement("sleeve", 825), + ), + ), + ), ) assertThat(actualStoreProduct?.storeProductMeta?.additionalInfo?.modelInfo).isEqualTo( mutableMapOf( @@ -77,17 +76,17 @@ class StoreProductJsonParserTest { "size" to "38", "waist" to 56, "bust" to 78, - "height" to 165 - ) + "height" to 165, + ), ) assertThat( - actualStoreProduct?.storeProductMeta?.additionalInfo?.fit + actualStoreProduct?.storeProductMeta?.additionalInfo?.fit, ).isEqualTo("regular") assertThat( - actualStoreProduct?.storeProductMeta?.additionalInfo?.style + actualStoreProduct?.storeProductMeta?.additionalInfo?.style, ).isEqualTo("fashionable") assertThat(actualStoreProduct?.storeProductMeta?.additionalInfo?.brandSizing).isEqualTo( - BrandSizing("large", false) + BrandSizing("large", false), ) } diff --git a/virtusize/src/test/java/com/virtusize/android/data/parsers/StoreProductMetaJsonParserTest.kt b/virtusize/src/test/java/com/virtusize/android/parsers/StoreProductMetaJsonParserTest.kt similarity index 91% rename from virtusize/src/test/java/com/virtusize/android/data/parsers/StoreProductMetaJsonParserTest.kt rename to virtusize/src/test/java/com/virtusize/android/parsers/StoreProductMetaJsonParserTest.kt index 36a9f1d3..1b473af0 100644 --- a/virtusize/src/test/java/com/virtusize/android/data/parsers/StoreProductMetaJsonParserTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/parsers/StoreProductMetaJsonParserTest.kt @@ -9,7 +9,6 @@ import org.json.JSONObject import org.junit.Test class StoreProductMetaJsonParserTest { - @Test fun parse_validJsonData_shouldReturnExpectedStoreProductMeta() { val actualStoreProductMeta = @@ -24,18 +23,18 @@ class StoreProductMetaJsonParserTest { mutableSetOf( Measurement("height", 760), Measurement("bust", 660), - Measurement("sleeve", 845) - ) + Measurement("sleeve", 845), + ), ), ProductSize( "36", mutableSetOf( Measurement("height", 750), Measurement("bust", 645), - Measurement("sleeve", 825) - ) - ) - ) + Measurement("sleeve", 825), + ), + ), + ), ) assertThat(actualStoreProductMeta?.additionalInfo?.modelInfo).isEqualTo( mutableMapOf( @@ -43,16 +42,16 @@ class StoreProductMetaJsonParserTest { "size" to "38", "waist" to 56, "bust" to 78, - "height" to 165 - ) + "height" to 165, + ), ) assertThat(actualStoreProductMeta?.additionalInfo?.fit).isEqualTo("loose") assertThat(actualStoreProductMeta?.additionalInfo?.style).isEqualTo("fashionable") assertThat(actualStoreProductMeta?.additionalInfo?.brandSizing).isEqualTo( BrandSizing( "small", - true - ) + true, + ), ) } @@ -65,8 +64,9 @@ class StoreProductMetaJsonParserTest { } companion object { - private val STORE_PRODUCT_META_JSON_DATA = JSONObject( - """ + private val STORE_PRODUCT_META_JSON_DATA = + JSONObject( + """ { "id":123, "modelInfo":null, @@ -123,7 +123,7 @@ class StoreProductMetaJsonParserTest { "style":null, "storeProduct":12345 } - """.trimIndent() - ) + """.trimIndent(), + ) } } diff --git a/virtusize/src/test/java/com/virtusize/android/util/ExtensionsKtTest.kt b/virtusize/src/test/java/com/virtusize/android/util/ExtensionsKtTest.kt index 936f6341..2c19ee43 100644 --- a/virtusize/src/test/java/com/virtusize/android/util/ExtensionsKtTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/util/ExtensionsKtTest.kt @@ -12,7 +12,6 @@ import org.robolectric.annotation.Config @RunWith(RobolectricTestRunner::class) @Config(sdk = [Build.VERSION_CODES.Q]) class ExtensionsKtTest { - private val context: Context = ApplicationProvider.getApplicationContext() @Test diff --git a/virtusize/src/test/java/com/virtusize/android/util/VirtusizeUtilsTest.kt b/virtusize/src/test/java/com/virtusize/android/util/VirtusizeUtilsTest.kt index c60ea91c..97540112 100644 --- a/virtusize/src/test/java/com/virtusize/android/util/VirtusizeUtilsTest.kt +++ b/virtusize/src/test/java/com/virtusize/android/util/VirtusizeUtilsTest.kt @@ -9,14 +9,14 @@ import com.virtusize.android.fixtures.ProductFixtures import org.junit.Test class VirtusizeUtilsTest { - @Test fun getStoreProductFitInfo_withUserShirtXS_shouldHaveExpectedFitInfo() { - val actualStoreProductFitInfo = VirtusizeUtils.getProductComparisonFitInfo( - userShirtXS.sizes[0], - storeShirt.sizes[0], - shirtProductWeights - ) + val actualStoreProductFitInfo = + VirtusizeUtils.getProductComparisonFitInfo( + userShirtXS.sizes[0], + storeShirt.sizes[0], + shirtProductWeights, + ) assertThat(actualStoreProductFitInfo.fitScore).isEqualTo(88.75f) assertThat(actualStoreProductFitInfo.isSmaller).isFalse() @@ -24,11 +24,12 @@ class VirtusizeUtilsTest { @Test fun getStoreProductFitInfo_withUserShirtS_shouldHaveExpectedFitInfo() { - val actualStoreProductFitInfo = VirtusizeUtils.getProductComparisonFitInfo( - userShirtS.sizes[0], - storeShirt.sizes[0], - shirtProductWeights - ) + val actualStoreProductFitInfo = + VirtusizeUtils.getProductComparisonFitInfo( + userShirtS.sizes[0], + storeShirt.sizes[0], + shirtProductWeights, + ) assertThat(actualStoreProductFitInfo.fitScore).isEqualTo(95.25f) assertThat(actualStoreProductFitInfo.isSmaller).isFalse() @@ -36,11 +37,12 @@ class VirtusizeUtilsTest { @Test fun getStoreProductFitInfo_withUserShirtM_shouldHaveExpectedFitInfo() { - val actualStoreProductFitInfo = VirtusizeUtils.getProductComparisonFitInfo( - userShirtM.sizes[0], - storeShirt.sizes[0], - shirtProductWeights - ) + val actualStoreProductFitInfo = + VirtusizeUtils.getProductComparisonFitInfo( + userShirtM.sizes[0], + storeShirt.sizes[0], + shirtProductWeights, + ) assertThat(actualStoreProductFitInfo.fitScore).isEqualTo(96.25f) assertThat(actualStoreProductFitInfo.isSmaller).isTrue() @@ -48,11 +50,12 @@ class VirtusizeUtilsTest { @Test fun getStoreProductFitInfo_withUserShirtL_shouldHaveExpectedFitInfo() { - val actualStoreProductFitInfo = VirtusizeUtils.getProductComparisonFitInfo( - userShirtL.sizes[0], - storeShirt.sizes[0], - shirtProductWeights - ) + val actualStoreProductFitInfo = + VirtusizeUtils.getProductComparisonFitInfo( + userShirtL.sizes[0], + storeShirt.sizes[0], + shirtProductWeights, + ) assertThat(actualStoreProductFitInfo.fitScore).isEqualTo(89.75f) assertThat(actualStoreProductFitInfo.isSmaller).isTrue() @@ -60,12 +63,12 @@ class VirtusizeUtilsTest { @Test fun getFindBestMatch_shouldReturnExpectedRecommendedSize() { - - val userProductRecommendedSize = VirtusizeUtils.findBestFitProductSize( - userShirts, - storeShirt, - ProductFixtures.productTypes() - ) + val userProductRecommendedSize = + VirtusizeUtils.findBestFitProductSize( + userShirts, + storeShirt, + ProductFixtures.productTypes(), + ) assertThat(userProductRecommendedSize?.bestFitScore).isEqualTo(96.25f) assertThat(userProductRecommendedSize?.bestUserProduct).isEqualTo(userShirts[2]) @@ -73,96 +76,104 @@ class VirtusizeUtilsTest { } private companion object { - - val userShirtXS = getProduct( - 1, - mutableListOf( - ProductSize( - "XS", - mutableSetOf( - Measurement("bust", 500), - Measurement("sleeve", 730), - Measurement("height", 665), - Measurement("shoulder", 400), - Measurement("hem", 610), - ) - ) + val userShirtXS = + getProduct( + 1, + mutableListOf( + ProductSize( + "XS", + mutableSetOf( + Measurement("bust", 500), + Measurement("sleeve", 730), + Measurement("height", 665), + Measurement("shoulder", 400), + Measurement("hem", 610), + ), + ), + ), ) - ) - - val userShirtS = getProduct( - 2, - mutableListOf( - ProductSize( - "S", - mutableSetOf( - Measurement("bust", 520), - Measurement("sleeve", 745), - Measurement("height", 685), - Measurement("shoulder", 410), - Measurement("hem", 630), - ) - ) + + val userShirtS = + getProduct( + 2, + mutableListOf( + ProductSize( + "S", + mutableSetOf( + Measurement("bust", 520), + Measurement("sleeve", 745), + Measurement("height", 685), + Measurement("shoulder", 410), + Measurement("hem", 630), + ), + ), + ), ) - ) - - val userShirtM = getProduct( - 3, - mutableListOf( - ProductSize( - "M", - mutableSetOf( - Measurement("bust", 540), - Measurement("sleeve", 760), - Measurement("height", 705), - Measurement("shoulder", 420), - Measurement("hem", 650), - ) - ) + + val userShirtM = + getProduct( + 3, + mutableListOf( + ProductSize( + "M", + mutableSetOf( + Measurement("bust", 540), + Measurement("sleeve", 760), + Measurement("height", 705), + Measurement("shoulder", 420), + Measurement("hem", 650), + ), + ), + ), ) - ) - - val userShirtL = getProduct( - 4, - mutableListOf( - ProductSize( - "L", - mutableSetOf( - Measurement("bust", 570), - Measurement("sleeve", 775), - Measurement("height", 725), - Measurement("shoulder", 430), - Measurement("hem", 680), - ) - ) + + val userShirtL = + getProduct( + 4, + mutableListOf( + ProductSize( + "L", + mutableSetOf( + Measurement("bust", 570), + Measurement("sleeve", 775), + Measurement("height", 725), + Measurement("shoulder", 430), + Measurement("hem", 680), + ), + ), + ), ) - ) val userShirts = mutableListOf(userShirtXS, userShirtS, userShirtM, userShirtL) - val storeShirt = getProduct( - 123, - mutableListOf( - ProductSize( - "Free", - mutableSetOf( - Measurement("bust", 530), - Measurement("sleeve", 770), - Measurement("height", 690), - Measurement("shoulder", 430), - Measurement("collar", 360), - ) - ) + val storeShirt = + getProduct( + 123, + mutableListOf( + ProductSize( + "Free", + mutableSetOf( + Measurement("bust", 530), + Measurement("sleeve", 770), + Measurement("height", 690), + Measurement("shoulder", 430), + Measurement("collar", 360), + ), + ), + ), ) - ) - val shirtProductWeights = mutableSetOf( - Weight("bust", 2f), - Weight("sleeve", 1f), - Weight("height", 0.5f) - ) + val shirtProductWeights = + mutableSetOf( + Weight("bust", 2f), + Weight("sleeve", 1f), + Weight("height", 0.5f), + ) - fun getProduct(productId: Int, sizes: List): Product { + fun getProduct( + productId: Int, + sizes: List, + ): Product { return Product( productId, sizes, @@ -172,7 +183,7 @@ class VirtusizeUtilsTest { "", null, 2, - null + null, ) } } From 532a09b4a2927a0d1859e1aeafd94c7870b14273 Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sat, 3 Aug 2024 21:41:53 +0900 Subject: [PATCH 04/19] Migrate from Groovy to Kotlin --- build.gradle | 67 ----- build.gradle.kts | 23 ++ buildSrc/build.gradle.kts | 7 + .../java/com/virtusize/android/Constants.kt | 24 ++ gradle.properties | 30 +- gradle/deploy.gradle | 90 ------ gradle/githooks.gradle | 8 - gradle/githooks.gradle.kts | 10 + gradle/ktlint.gradle | 24 -- gradle/ktlint.gradle.kts | 42 +++ gradle/libs.versions.toml | 43 +++ gradle/release.gradle | 12 - gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 257 +++++++----------- jitpack.yml | 2 +- sampleAppKotlin/build.gradle | 51 ---- sampleAppKotlin/build.gradle.kts | 54 ++++ sampleappjava/build.gradle | 50 ---- sampleappjava/build.gradle.kts | 50 ++++ settings.gradle.kts | 27 +- virtusize-core/build.gradle | 70 ----- virtusize-core/build.gradle.kts | 146 ++++++++++ virtusize/build.gradle | 77 ------ virtusize/build.gradle.kts | 150 ++++++++++ 25 files changed, 700 insertions(+), 616 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/java/com/virtusize/android/Constants.kt delete mode 100644 gradle/deploy.gradle delete mode 100644 gradle/githooks.gradle create mode 100644 gradle/githooks.gradle.kts delete mode 100644 gradle/ktlint.gradle create mode 100644 gradle/ktlint.gradle.kts create mode 100644 gradle/libs.versions.toml delete mode 100644 gradle/release.gradle delete mode 100644 sampleAppKotlin/build.gradle create mode 100644 sampleAppKotlin/build.gradle.kts delete mode 100644 sampleappjava/build.gradle create mode 100644 sampleappjava/build.gradle.kts delete mode 100644 virtusize-core/build.gradle create mode 100644 virtusize-core/build.gradle.kts delete mode 100644 virtusize/build.gradle create mode 100644 virtusize/build.gradle.kts diff --git a/build.gradle b/build.gradle deleted file mode 100644 index d2cdac71..00000000 --- a/build.gradle +++ /dev/null @@ -1,67 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - ext { - gradle_version = '8.5.0' - kotlin_version = '1.8.22' - } - - repositories { - google() - mavenCentral() - } - - dependencies { - classpath "com.android.tools.build:gradle:$gradle_version" - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -plugins { - id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' -} - -allprojects { - group = GROUP_ID - version = VERSION_NAME - - repositories { - google() - mavenCentral() - maven { url "https://github.com/virtusize/virtusize_auth_android/raw/main"} - maven { url "https://jitpack.io" } - } -} - -ext { - compileSdkVersion = 34 - minSdkVersion = 21 - targetSdkVersion = 34 - - appcompat_version = '1.6.1' - build_version = '28.0.0' - constraint_layout_version = '2.1.4' - coroutine_version = '1.6.4' - junit_version = '4.13.2' - ktlint_version = '0.42.1' - lifecycle_version = '2.6.2' -} - -task clean(type: Delete) { - delete rootProject.buildDir -} - -def localProperties(String key) { - Properties properties = new Properties() - File localPropertiesFile = project.rootProject.file("local.properties") - if (localPropertiesFile.exists()) { - properties.load(localPropertiesFile.newDataInputStream()) - return properties.getProperty(key) ?: "" - } else { - return System.getenv(key) ?: "" - } -} - -apply from: rootProject.file('gradle/githooks.gradle') -apply from: rootProject.file('gradle/release.gradle') \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..4a58fc33 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,23 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.jetbrains.kotlin.android) apply false + alias(libs.plugins.kotlin.parcelize) apply false + alias(libs.plugins.nexus.publish) +} + +apply(from = "${project.rootDir}/gradle/githooks.gradle.kts") +apply(from = "${project.rootDir}/gradle/ktlint.gradle.kts") + +nexusPublishing { + repositories { + sonatype { + // only for users registered in Sonatype after 24 Feb 2021 + nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) + snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) + username = System.getenv("OSSRH_USERNAME") + password = System.getenv("OSSRH_PASSWORD") + } + } +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts new file mode 100644 index 00000000..876c922b --- /dev/null +++ b/buildSrc/build.gradle.kts @@ -0,0 +1,7 @@ +plugins { + `kotlin-dsl` +} + +repositories { + mavenCentral() +} diff --git a/buildSrc/src/main/java/com/virtusize/android/Constants.kt b/buildSrc/src/main/java/com/virtusize/android/Constants.kt new file mode 100644 index 00000000..293dccc7 --- /dev/null +++ b/buildSrc/src/main/java/com/virtusize/android/Constants.kt @@ -0,0 +1,24 @@ +package com.virtusize.android + +object Constants { + const val COMPILE_SDK = 34 + const val MIN_SDK = 21 + const val TARGET_SDK = 34 + + // Update versionName when publishing a new release + const val VERSION_NAME = "2.5.5" + const val GROUP_ID = "com.virtusize.android" + const val POM_URL = "https://github.com/virtusize/integration_android" + + object Virtusize { + const val ARTIFACT_ID = "virtusize" + const val ARTIFACT_NAME = "virtusize" + const val ARTIFACT_DESCRIPTION = "Virtusize Android SDK" + } + + object VirtusizeCore { + const val ARTIFACT_ID = "virtusize-core" + const val ARTIFACT_NAME = "virtusize-core" + const val ARTIFACT_DESCRIPTION = "The core module of Virtusize Android SDK" + } +} diff --git a/gradle.properties b/gradle.properties index 4e1064cd..20e2a015 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,15 +1,23 @@ -GROUP_ID=com.virtusize.android -# Update VERSION_NAME when publishing a new release -VERSION_NAME=2.5.5 - -POM_URL=https://github.com/virtusize/integration_android - +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official -# Enable AndroidX -android.enableJetifier=true -android.useAndroidX=true -android.defaults.buildfeatures.buildconfig=true \ No newline at end of file +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/deploy.gradle b/gradle/deploy.gradle deleted file mode 100644 index 87fce9eb..00000000 --- a/gradle/deploy.gradle +++ /dev/null @@ -1,90 +0,0 @@ -apply plugin: 'maven-publish' -apply plugin: 'signing' - -android { - publishing { - singleVariant("release") { - withSourcesJar() - // TODO: Fix error. See: https://github.com/Kotlin/dokka/issues/2956 - // withJavadocJar() - } - } -} - -// Because the components are created only during the afterEvaluate phase, you must -// configure your publications using the afterEvaluate() lifecycle method. -afterEvaluate { - publishing { - publications { - // Creates a Maven publication called "release". - release(MavenPublication) { - // You can then customize attributes of the publication as shown below. - groupId = rootProject.group - artifactId = project.artifactId - version = rootProject.version - - // Applies the component for the release build variant. - afterEvaluate { - from components.release - } - - pom { - name = project.artifactName - packaging = 'aar' - description = project.artifactDescription - url = POM_URL - - licenses { - license { - name = "The MIT License" - url = 'https://raw.githubusercontent.com/virtusize/integration_android/master/LICENSE' - distribution = 'repo' - } - } - - developers { - developer { - id = 'virtusize' - name = 'Virtusize' - } - } - - scm { - connection = 'scm:git@github.com/virtusize/integration_android.git' - developerConnection = 'scm:git@github.com/virtusize/integration_android.git' - url = POM_URL - } - - } - } - } - - repositories { - maven { - def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" - def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - credentials { - username = localProperties('OSSRH_USERNAME') - password = localProperties('OSSRH_PASSWORD') - } - } - } - } - - def shouldSign = !localProperties('GPG_KEY_ID').isEmpty() - && !localProperties('GPG_KEY').isEmpty() - && !localProperties('GPG_KEY_PASSWORD').isEmpty() - - signing { - required { shouldSign && gradle.taskGraph.hasTask("publish") } - if (shouldSign) { - useInMemoryPgpKeys( - localProperties('GPG_KEY_ID'), - localProperties('GPG_KEY'), - localProperties('GPG_KEY_PASSWORD') - ) - } - sign publishing.publications.release - } -} diff --git a/gradle/githooks.gradle b/gradle/githooks.gradle deleted file mode 100644 index 6f75dfc1..00000000 --- a/gradle/githooks.gradle +++ /dev/null @@ -1,8 +0,0 @@ -task installGitHooks(type: Copy) { - from new File(rootProject.rootDir, '.githooks/pre-push') - into { new File(rootProject.rootDir, '.git/hooks') } - fileMode 0775 -} - -/* Uncomment the following line if you want the installGitHooks task to be run automatically */ -// tasks.getByPath(':virtusize:preBuild').dependsOn installGitHooks \ No newline at end of file diff --git a/gradle/githooks.gradle.kts b/gradle/githooks.gradle.kts new file mode 100644 index 00000000..8b0d07b7 --- /dev/null +++ b/gradle/githooks.gradle.kts @@ -0,0 +1,10 @@ +tasks.register("installGitHooks") { + from(File(rootProject.rootDir, ".githooks/pre-push")) + into(File(rootProject.rootDir, ".git/hooks")) + fileMode = "0775".toInt(8) // Octal representation of file mode +} + +// Uncomment the following line if you want the installGitHooks task to be run automatically +// afterEvaluate { +// tasks.getByPath(":virtusize:preBuild").dependsOn(tasks.named("installGitHooks")) +// } diff --git a/gradle/ktlint.gradle b/gradle/ktlint.gradle deleted file mode 100644 index 53c39cb9..00000000 --- a/gradle/ktlint.gradle +++ /dev/null @@ -1,24 +0,0 @@ -configurations { - ktlint -} - -dependencies { - ktlint "com.pinterest:ktlint:$ktlint_version" -} - -task ktlint(type: JavaExec, group: "verification") { - description = "Check Kotlin code style." - classpath = configurations.ktlint - main = "com.pinterest.ktlint.Main" - args "src/**/*.kt", - // to generate report in checkstyle format prepend following args: - "--reporter=plain", "--reporter=html,output=${buildDir}/ktlint.html" -} -check.dependsOn ktlint - -task ktlintFormat(type: JavaExec, group: "formatting") { - description = "Fix Kotlin code style deviations." - classpath = configurations.ktlint - main = "com.pinterest.ktlint.Main" - args "-F", "src/**/*.kt" -} \ No newline at end of file diff --git a/gradle/ktlint.gradle.kts b/gradle/ktlint.gradle.kts new file mode 100644 index 00000000..3bba7b1f --- /dev/null +++ b/gradle/ktlint.gradle.kts @@ -0,0 +1,42 @@ +private val ktlint: Configuration by configurations.creating + +private fun Project.dependencyFromLibs(name: String) = the().named("libs").findLibrary(name).get() + +dependencies { + ktlint(dependencyFromLibs("ktlint")) { + attributes { + attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL)) + } + } +} + +tasks.register("ktlint") { + group = LifecycleBasePlugin.VERIFICATION_GROUP + description = "Check Kotlin code style." + classpath = ktlint + mainClass.set("com.pinterest.ktlint.Main") + args( + "**/src/**/*.kt", + "**.kts", + "!**/build/**", + // Do not run with option "--log-level=debug" or "--log-level=trace" as the lint violations will be difficult + // to spot between the amount of output lines. + ) +} + +tasks.register("ktlintFormat") { + group = LifecycleBasePlugin.VERIFICATION_GROUP + description = "Fix Kotlin code style deviations." + classpath = ktlint + mainClass.set("com.pinterest.ktlint.Main") + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + jvmArgs("--add-opens=java.base/java.lang=ALL-UNNAMED") + args( + "-F", + "**/src/**/*.kt", + "**.kts", + "!**/build/**", + // Do not run with option "--log-level=debug" or "--log-level=trace" as the lint violations will be difficult + // to spot between the amount of output lines. + ) +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..79c07e78 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,43 @@ +[versions] +agp = "8.5.1" +androidxTestCore = "1.6.1" +appcompat = "1.7.0" +coreKtx = "1.13.1" +espressoCore = "3.6.1" +json = "20230227" +junit = "4.13.2" +junitVersion = "1.2.1" +kotlin = "2.0.0" +kotlinxCoroutinesTest = "1.8.1" +ktlint = "1.2.1" +lifecycleViewmodelKtx = "2.8.4" +material = "1.12.0" +nextPublish = "1.1.0" +robolectric = "4.13" +truth = "1.4.4" +virtusize = "2.5.5" +virtusizeAuth = "1.0.4" + +[libraries] +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } +androidx-test-core = { group = "androidx.test", name = "core", version.ref = "androidxTestCore" } +json = { module = "org.json:json", version.ref = "json" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" } +ktlint = { module = "com.pinterest.ktlint:ktlint-cli", version.ref = "ktlint" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" } +truth = { group = "com.google.truth", name = "truth", version.ref = "truth" } +virtusize = { group = "com.virtusize.android", name = "virtusize", version.ref = "virtusize" } +virtusize-auth = { module = "com.virtusize.android:virtusize-auth", version.ref = "virtusizeAuth" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +android-library = { id = "com.android.library", version.ref = "agp" } +jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } +nexus-publish = { id = "io.github.gradle-nexus.publish-plugin", version.ref = "nextPublish" } diff --git a/gradle/release.gradle b/gradle/release.gradle deleted file mode 100644 index 167e98fa..00000000 --- a/gradle/release.gradle +++ /dev/null @@ -1,12 +0,0 @@ -// Publishing to Maven Central via Sonatype OSSRH -nexusPublishing { - repositories { - sonatype { - //only for users registered in Sonatype after 24 Feb 2021 - nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) - snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) - username = localProperties('OSSRH_USERNAME') - password = localProperties('OSSRH_PASSWORD') - } - } -} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f 100644 GIT binary patch delta 17845 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 X49Luk)baa85H9$5YCsTPT`SVRWMtMW delta 18435 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 B8dv}T diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8aaa0e0a..ad0c0717 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,4 +1,4 @@ -#Fri Sep 15 13:09:48 IST 2023 +#Sat Aug 03 17:16:17 JST 2024 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip diff --git a/gradlew b/gradlew index 1b6c7873..4f906e0c 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/bin/sh +#!/usr/bin/env sh # -# Copyright © 2015-2021 the original authors. +# Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,101 +17,67 @@ # ############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# +## +## Gradle start up script for UN*X +## ############################################################################## # Attempt to set APP_HOME - # Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi done - -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" -APP_BASE_NAME=${0##*/} +APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum +MAX_FD="maximum" warn () { echo "$*" -} >&2 +} die () { echo echo "$*" echo exit 1 -} >&2 +} # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -121,9 +87,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java + JAVACMD="$JAVA_HOME/jre/sh/java" else - JAVACMD=$JAVA_HOME/bin/java + JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -132,7 +98,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD=java + JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -140,95 +106,80 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi fi -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi # For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg + i=`expr $i + 1` done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ - "$@" - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" diff --git a/jitpack.yml b/jitpack.yml index f78f664d..57db2961 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -1 +1 @@ -jdk: openjdk11 \ No newline at end of file +jdk: openjdk17 \ No newline at end of file diff --git a/sampleAppKotlin/build.gradle b/sampleAppKotlin/build.gradle deleted file mode 100644 index d4157611..00000000 --- a/sampleAppKotlin/build.gradle +++ /dev/null @@ -1,51 +0,0 @@ -plugins { - id 'com.android.application' - id 'kotlin-android' - id 'kotlin-parcelize' -} - -android { - compileSdk rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "com.virtusize.sampleAppKotlin" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - debug { - debuggable true - minifyEnabled true - shrinkResources true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - buildFeatures { - viewBinding true - } - namespace 'com.virtusize.sampleappkotlin' -} - -dependencies { - implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "androidx.appcompat:appcompat:$appcompat_version" - implementation "androidx.constraintlayout:constraintlayout:$constraint_layout_version" - implementation 'com.google.android.material:material:1.9.0' - - // implementation "com.virtusize.android:virtusize:${project.version}" - implementation project(":virtusize") -} diff --git a/sampleAppKotlin/build.gradle.kts b/sampleAppKotlin/build.gradle.kts new file mode 100644 index 00000000..622a290d --- /dev/null +++ b/sampleAppKotlin/build.gradle.kts @@ -0,0 +1,54 @@ +import com.virtusize.android.Constants + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) +} + +android { + namespace = "com.virtusize.sampleappkotlin" + compileSdk = Constants.COMPILE_SDK + + defaultConfig { + applicationId = "com.virtusize.sampleappkotlin" + minSdk = Constants.MIN_SDK + targetSdk = Constants.TARGET_SDK + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + buildFeatures { + viewBinding = true + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + +// implementation(libs.virtusize) + implementation(project(":virtusize")) + implementation(libs.virtusize.auth) +} diff --git a/sampleappjava/build.gradle b/sampleappjava/build.gradle deleted file mode 100644 index 48f7e4c5..00000000 --- a/sampleappjava/build.gradle +++ /dev/null @@ -1,50 +0,0 @@ -plugins { - id 'com.android.application' -} - -android { - compileSdk rootProject.ext.compileSdkVersion - defaultConfig { - applicationId "com.virtusize.sampleappjava" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 1 - versionName "1.0" - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - - buildTypes { - release { - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - debug { - debuggable true - minifyEnabled true - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - namespace 'com.virtusize.sampleappjava' -} - -dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - - // Add the following dependency on Kotlin standard library to support the Virtusize SDK for a Java project - implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - - implementation "androidx.appcompat:appcompat:$appcompat_version" - implementation "androidx.constraintlayout:constraintlayout:$constraint_layout_version" - - // implementation "com.virtusize.android:virtusize:${project.version}" - implementation project(":virtusize") - - testImplementation "junit:junit:$junit_version" - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' -} diff --git a/sampleappjava/build.gradle.kts b/sampleappjava/build.gradle.kts new file mode 100644 index 00000000..7979f330 --- /dev/null +++ b/sampleappjava/build.gradle.kts @@ -0,0 +1,50 @@ +import com.virtusize.android.Constants + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) +} + +android { + namespace = "com.virtusize.sampleappjava" + compileSdk = Constants.COMPILE_SDK + + defaultConfig { + applicationId = "com.virtusize.sampleappjava" + minSdk = Constants.MIN_SDK + targetSdk = Constants.TARGET_SDK + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + +// implementation(libs.virtusize) + implementation(project(":virtusize")) + implementation(libs.virtusize.auth) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 1fe5e7bc..6aed51db 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,29 @@ +pluginManagement { + repositories { + google { + content { + includeGroupByRegex("com\\.android.*") + includeGroupByRegex("com\\.google.*") + includeGroupByRegex("androidx.*") + } + } + mavenCentral() + gradlePluginPortal() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url = uri("https://github.com/virtusize/virtusize_auth_android/raw/main") } + maven { url = uri("https://jitpack.io") } + } +} + +rootProject.name = "integration_android" + include(":virtusize") include(":virtusize-core") +include(":sampleappjava") include(":sampleAppKotlin") -include(":sampleappjava") \ No newline at end of file diff --git a/virtusize-core/build.gradle b/virtusize-core/build.gradle deleted file mode 100644 index 1578a733..00000000 --- a/virtusize-core/build.gradle +++ /dev/null @@ -1,70 +0,0 @@ -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'kotlin-parcelize' -} - -android { - compileSdk rootProject.ext.compileSdkVersion - - defaultConfig { - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - debug { - buildConfigField "String", "VERSION_NAME", "\"${VERSION_NAME}\"" - } - - release { - buildConfigField "String", "VERSION_NAME", "\"${VERSION_NAME}\"" - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = '17' - } - - testOptions { - unitTests { - includeAndroidResources = true - returnDefaultValues = true - } - } - namespace 'com.virtusize.android.core' -} - -dependencies { - implementation 'androidx.core:core-ktx:1.12.0' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.9.0' - - testImplementation 'androidx.test:core:1.5.0' - testImplementation "com.google.truth:truth:1.1.2" - testImplementation 'junit:junit:4.+' - testImplementation 'org.json:json:20210307' - testImplementation 'org.robolectric:robolectric:4.10.3' - - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' -} - -ext { - artifactId = "virtusize-core" - artifactName = "virtusize-core" - artifactDescription = "The core module of Virtusize Android SDK" -} - -apply from: "${rootDir}/gradle/ktlint.gradle" -apply from: "${rootDir}/gradle/deploy.gradle" diff --git a/virtusize-core/build.gradle.kts b/virtusize-core/build.gradle.kts new file mode 100644 index 00000000..58da36fd --- /dev/null +++ b/virtusize-core/build.gradle.kts @@ -0,0 +1,146 @@ +import com.virtusize.android.Constants + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.kotlin.parcelize) + `maven-publish` + signing +} + +android { + namespace = "com.virtusize.android.core" + compileSdk = Constants.COMPILE_SDK + + defaultConfig { + minSdk = Constants.MIN_SDK + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + debug { + buildConfigField("String", "VERSION_NANE", "\"${Constants.VERSION_NAME}\"") + } + release { + buildConfigField("String", "VERSION_NANE", "\"${Constants.VERSION_NAME}\"") + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + buildFeatures { + buildConfig = true + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + isReturnDefaultValues = true + } + } + + publishing { + singleVariant("release") { + withSourcesJar() + // TODO: Fix error. See: https://github.com/Kotlin/dokka/issues/2956 + // withJavadocJar() + } + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + + testImplementation(libs.androidx.test.core) + testImplementation(libs.junit) + testImplementation(libs.json) + testImplementation(libs.robolectric) + testImplementation(libs.truth) +} + +publishing { + repositories { + maven { + val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" + url = uri(if (Constants.VERSION_NAME.endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) + credentials { + username = System.getenv("OSSRH_USERNAME") + password = System.getenv("OSSRH_PASSWORD") + } + } + } + + publications { + create("maven") { + groupId = Constants.GROUP_ID + artifactId = Constants.VirtusizeCore.ARTIFACT_ID + version = Constants.VERSION_NAME + + afterEvaluate { + from(components["release"]) + } + + pom { + name = Constants.VirtusizeCore.ARTIFACT_NAME + packaging = "aar" + description = Constants.VirtusizeCore.ARTIFACT_DESCRIPTION + url = Constants.POM_URL + + licenses { + license { + name = "The MIT License" + url = "https://raw.githubusercontent.com/virtusize/integration_android/master/LICENSE" + distribution = "repo" + } + } + + developers { + developer { + id = "virtusize" + name = "Virtusize" + } + } + + scm { + connection = "scm:git@github.com/virtusize/integration_android.git" + developerConnection = "scm:git@github.com/virtusize/integration_android.git" + url = Constants.POM_URL + } + } + } + } + + val shouldSign = + System.getenv("GPG_KEY_ID") != null && + System.getenv("GPG_KEY") != null && + System.getenv("GPG_KEY_PASSWORD") != null + + signing { + isRequired = shouldSign && gradle.taskGraph.hasTask("publish") + if (isRequired) { + useInMemoryPgpKeys( + System.getenv("GPG_KEY_ID"), + System.getenv("GPG_KEY"), + System.getenv("GPG_KEY_PASSWORD"), + ) + } + sign(publishing.publications["maven"]) + } +} diff --git a/virtusize/build.gradle b/virtusize/build.gradle deleted file mode 100644 index ffd75597..00000000 --- a/virtusize/build.gradle +++ /dev/null @@ -1,77 +0,0 @@ -plugins { - id 'com.android.library' - id 'kotlin-android' - id 'kotlin-parcelize' -} - -android { - compileSdk rootProject.ext.compileSdkVersion - - defaultConfig { - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - } - - buildTypes { - debug { - buildConfigField "String", "VERSION_NAME", "\"${VERSION_NAME}\"" - consumerProguardFiles 'proguard-rules.pro' - } - - release { - buildConfigField "String", "VERSION_NAME", "\"${VERSION_NAME}\"" - consumerProguardFiles 'proguard-rules.pro' - } - } - - testOptions { - unitTests { - includeAndroidResources = true - returnDefaultValues = true - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = '17' - } - - buildFeatures { - viewBinding true - } - namespace 'com.virtusize.android' -} - -dependencies { - api project(":virtusize-core") - api "com.virtusize.android:virtusize-auth:1.0.3" - - implementation "androidx.appcompat:appcompat:$appcompat_version" - implementation 'androidx.browser:browser:1.6.0' - implementation "androidx.cardview:cardview:1.0.0" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" - - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version" - - testImplementation "junit:junit:$junit_version" - testImplementation "com.google.truth:truth:1.1.2" - testImplementation 'org.json:json:20210307' - testImplementation 'org.robolectric:robolectric:4.10.3' - testImplementation 'androidx.test:core:1.5.0' - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine_version" -} - -ext { - artifactId = "virtusize" - artifactName = "virtusize" - artifactDescription = "Virtusize Android SDK" -} - -apply from: "${rootDir}/gradle/ktlint.gradle" -apply from: "${rootDir}/gradle/deploy.gradle" \ No newline at end of file diff --git a/virtusize/build.gradle.kts b/virtusize/build.gradle.kts new file mode 100644 index 00000000..3fcb716a --- /dev/null +++ b/virtusize/build.gradle.kts @@ -0,0 +1,150 @@ +import com.virtusize.android.Constants + +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.jetbrains.kotlin.android) + `maven-publish` + signing +} + +android { + namespace = "com.virtusize.android" + compileSdk = Constants.COMPILE_SDK + + defaultConfig { + minSdk = Constants.MIN_SDK + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + debug { + buildConfigField("String", "VERSION_NANE", "\"${Constants.VERSION_NAME}\"") + } + release { + buildConfigField("String", "VERSION_NANE", "\"${Constants.VERSION_NAME}\"") + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro", + ) + } + } + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } + + buildFeatures { + buildConfig = true + viewBinding = true + } + + testOptions { + unitTests { + isIncludeAndroidResources = true + isReturnDefaultValues = true + } + } + + publishing { + singleVariant("release") { + withSourcesJar() + // TODO: Fix error. See: https://github.com/Kotlin/dokka/issues/2956 + // withJavadocJar() + } + } +} + +dependencies { + api(project(":virtusize-core")) + implementation(libs.virtusize.auth) + + implementation(libs.androidx.appcompat) + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.viewmodel.ktx) + implementation(libs.material) + + testImplementation(libs.androidx.test.core) + testImplementation(libs.junit) + testImplementation(libs.json) + testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.robolectric) + testImplementation(libs.truth) +} + +publishing { + repositories { + maven { + val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" + url = uri(if (Constants.VERSION_NAME.endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) + credentials { + username = System.getenv("OSSRH_USERNAME") + password = System.getenv("OSSRH_PASSWORD") + } + } + } + + publications { + create("maven") { + groupId = Constants.GROUP_ID + artifactId = Constants.Virtusize.ARTIFACT_ID + version = Constants.VERSION_NAME + + afterEvaluate { + from(components["release"]) + } + + pom { + name = Constants.Virtusize.ARTIFACT_NAME + packaging = "aar" + description = Constants.Virtusize.ARTIFACT_DESCRIPTION + url = Constants.POM_URL + + licenses { + license { + name = "The MIT License" + url = "https://raw.githubusercontent.com/virtusize/integration_android/master/LICENSE" + distribution = "repo" + } + } + + developers { + developer { + id = "virtusize" + name = "Virtusize" + } + } + + scm { + connection = "scm:git@github.com/virtusize/integration_android.git" + developerConnection = "scm:git@github.com/virtusize/integration_android.git" + url = Constants.POM_URL + } + } + } + } + + val shouldSign = + System.getenv("GPG_KEY_ID") != null && + System.getenv("GPG_KEY") != null && + System.getenv("GPG_KEY_PASSWORD") != null + + signing { + isRequired = shouldSign && gradle.taskGraph.hasTask("publish") + if (isRequired) { + useInMemoryPgpKeys( + System.getenv("GPG_KEY_ID"), + System.getenv("GPG_KEY"), + System.getenv("GPG_KEY_PASSWORD"), + ) + } + sign(publishing.publications["maven"]) + } +} From b53618ca015e3aed63392e5eb12d493b851159e2 Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sat, 3 Aug 2024 21:42:35 +0900 Subject: [PATCH 05/19] Remove .editorconfig --- virtusize/.editorconfig | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 virtusize/.editorconfig diff --git a/virtusize/.editorconfig b/virtusize/.editorconfig deleted file mode 100644 index eb52868e..00000000 --- a/virtusize/.editorconfig +++ /dev/null @@ -1,4 +0,0 @@ -[*.{kt,kts}] -indent_size=4 -insert_final_newline=true -max_line_length=100 \ No newline at end of file From 094081b2c583e80474bfd7c8c195c7b246f6918c Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sat, 3 Aug 2024 21:51:03 +0900 Subject: [PATCH 06/19] Create Project extension function getProperties --- build.gradle.kts | 6 ++++-- .../virtusize/android/ProjectExtensions.kt | 20 +++++++++++++++++++ virtusize-core/build.gradle.kts | 17 ++++++++-------- virtusize/build.gradle.kts | 17 ++++++++-------- 4 files changed, 42 insertions(+), 18 deletions(-) create mode 100644 buildSrc/src/main/java/com/virtusize/android/ProjectExtensions.kt diff --git a/build.gradle.kts b/build.gradle.kts index 4a58fc33..82d6b9b9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,5 @@ +import com.virtusize.android.getProperties + // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { alias(libs.plugins.android.application) apply false @@ -16,8 +18,8 @@ nexusPublishing { // only for users registered in Sonatype after 24 Feb 2021 nexusUrl.set(uri("https://s01.oss.sonatype.org/service/local/")) snapshotRepositoryUrl.set(uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")) - username = System.getenv("OSSRH_USERNAME") - password = System.getenv("OSSRH_PASSWORD") + username = getProperties("OSSRH_USERNAME") + password = getProperties("OSSRH_PASSWORD") } } } diff --git a/buildSrc/src/main/java/com/virtusize/android/ProjectExtensions.kt b/buildSrc/src/main/java/com/virtusize/android/ProjectExtensions.kt new file mode 100644 index 00000000..fa06e5a1 --- /dev/null +++ b/buildSrc/src/main/java/com/virtusize/android/ProjectExtensions.kt @@ -0,0 +1,20 @@ +package com.virtusize.android + +import org.gradle.api.Project +import java.util.Properties + +/** + * Get the value of the key from the local.properties file. + * If the key is not found in the local.properties file, + * it will try to get the value from the environment variables. + */ +fun Project.getProperties(key: String): String? { + val localPropertiesFile = file("local.properties") + return if (localPropertiesFile.exists()) { + Properties().apply { + load(localPropertiesFile.inputStream()) + }.getProperty(key) + } else { + System.getenv(key) + } +} \ No newline at end of file diff --git a/virtusize-core/build.gradle.kts b/virtusize-core/build.gradle.kts index 58da36fd..ae715003 100644 --- a/virtusize-core/build.gradle.kts +++ b/virtusize-core/build.gradle.kts @@ -1,4 +1,5 @@ import com.virtusize.android.Constants +import com.virtusize.android.getProperties plugins { alias(libs.plugins.android.library) @@ -81,8 +82,8 @@ publishing { val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" url = uri(if (Constants.VERSION_NAME.endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) credentials { - username = System.getenv("OSSRH_USERNAME") - password = System.getenv("OSSRH_PASSWORD") + username = getProperties("OSSRH_USERNAME") + password = getProperties("OSSRH_PASSWORD") } } } @@ -128,17 +129,17 @@ publishing { } val shouldSign = - System.getenv("GPG_KEY_ID") != null && - System.getenv("GPG_KEY") != null && - System.getenv("GPG_KEY_PASSWORD") != null + getProperties("GPG_KEY_ID") != null && + getProperties("GPG_KEY") != null && + getProperties("GPG_KEY_PASSWORD") != null signing { isRequired = shouldSign && gradle.taskGraph.hasTask("publish") if (isRequired) { useInMemoryPgpKeys( - System.getenv("GPG_KEY_ID"), - System.getenv("GPG_KEY"), - System.getenv("GPG_KEY_PASSWORD"), + getProperties("GPG_KEY_ID"), + getProperties("GPG_KEY"), + getProperties("GPG_KEY_PASSWORD"), ) } sign(publishing.publications["maven"]) diff --git a/virtusize/build.gradle.kts b/virtusize/build.gradle.kts index 3fcb716a..c99a22f4 100644 --- a/virtusize/build.gradle.kts +++ b/virtusize/build.gradle.kts @@ -1,4 +1,5 @@ import com.virtusize.android.Constants +import com.virtusize.android.getProperties plugins { alias(libs.plugins.android.library) @@ -85,8 +86,8 @@ publishing { val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" url = uri(if (Constants.VERSION_NAME.endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) credentials { - username = System.getenv("OSSRH_USERNAME") - password = System.getenv("OSSRH_PASSWORD") + username = getProperties("OSSRH_USERNAME") + password = getProperties("OSSRH_PASSWORD") } } } @@ -132,17 +133,17 @@ publishing { } val shouldSign = - System.getenv("GPG_KEY_ID") != null && - System.getenv("GPG_KEY") != null && - System.getenv("GPG_KEY_PASSWORD") != null + getProperties("GPG_KEY_ID") != null && + getProperties("GPG_KEY") != null && + getProperties("GPG_KEY_PASSWORD") != null signing { isRequired = shouldSign && gradle.taskGraph.hasTask("publish") if (isRequired) { useInMemoryPgpKeys( - System.getenv("GPG_KEY_ID"), - System.getenv("GPG_KEY"), - System.getenv("GPG_KEY_PASSWORD"), + getProperties("GPG_KEY_ID"), + getProperties("GPG_KEY"), + getProperties("GPG_KEY_PASSWORD"), ) } sign(publishing.publications["maven"]) From 1ac068725a8f2104fe816b8581e3dbdea9525107 Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sat, 3 Aug 2024 21:53:18 +0900 Subject: [PATCH 07/19] Apply ktlint --- .../src/main/java/com/virtusize/android/ProjectExtensions.kt | 2 +- virtusize-core/build.gradle.kts | 4 ++-- virtusize/build.gradle.kts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/buildSrc/src/main/java/com/virtusize/android/ProjectExtensions.kt b/buildSrc/src/main/java/com/virtusize/android/ProjectExtensions.kt index fa06e5a1..84eb9953 100644 --- a/buildSrc/src/main/java/com/virtusize/android/ProjectExtensions.kt +++ b/buildSrc/src/main/java/com/virtusize/android/ProjectExtensions.kt @@ -17,4 +17,4 @@ fun Project.getProperties(key: String): String? { } else { System.getenv(key) } -} \ No newline at end of file +} diff --git a/virtusize-core/build.gradle.kts b/virtusize-core/build.gradle.kts index ae715003..c0c242c6 100644 --- a/virtusize-core/build.gradle.kts +++ b/virtusize-core/build.gradle.kts @@ -130,8 +130,8 @@ publishing { val shouldSign = getProperties("GPG_KEY_ID") != null && - getProperties("GPG_KEY") != null && - getProperties("GPG_KEY_PASSWORD") != null + getProperties("GPG_KEY") != null && + getProperties("GPG_KEY_PASSWORD") != null signing { isRequired = shouldSign && gradle.taskGraph.hasTask("publish") diff --git a/virtusize/build.gradle.kts b/virtusize/build.gradle.kts index c99a22f4..ef0b7b84 100644 --- a/virtusize/build.gradle.kts +++ b/virtusize/build.gradle.kts @@ -134,8 +134,8 @@ publishing { val shouldSign = getProperties("GPG_KEY_ID") != null && - getProperties("GPG_KEY") != null && - getProperties("GPG_KEY_PASSWORD") != null + getProperties("GPG_KEY") != null && + getProperties("GPG_KEY_PASSWORD") != null signing { isRequired = shouldSign && gradle.taskGraph.hasTask("publish") From d90695ae0eec4d54471270332d8d7588b23999d7 Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sat, 3 Aug 2024 22:04:02 +0900 Subject: [PATCH 08/19] Fix settings.gradle.kts --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 6aed51db..969175d9 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,5 +25,5 @@ rootProject.name = "integration_android" include(":virtusize") include(":virtusize-core") -include(":sampleappjava") +include(":sampleAppJava") include(":sampleAppKotlin") From f6632d5307fcac839942947861f94d438ac6d340 Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sat, 3 Aug 2024 22:09:11 +0900 Subject: [PATCH 09/19] Increasing the amount of JVM memory --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 20e2a015..f9d02186 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx4608m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. For more details, visit # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects From df928fd7c89256040cbc781da6dc255e6742e2b9 Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sun, 4 Aug 2024 00:16:54 +0900 Subject: [PATCH 10/19] Fix R8 error --- sampleAppKotlin/build.gradle.kts | 8 ++++++-- sampleappjava/build.gradle.kts | 8 ++++++-- virtusize-core/build.gradle.kts | 2 +- virtusize/build.gradle.kts | 4 ++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/sampleAppKotlin/build.gradle.kts b/sampleAppKotlin/build.gradle.kts index 622a290d..e452516c 100644 --- a/sampleAppKotlin/build.gradle.kts +++ b/sampleAppKotlin/build.gradle.kts @@ -21,11 +21,12 @@ android { buildTypes { release { - isMinifyEnabled = false + isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro", ) + signingConfig = signingConfigs.getByName("debug") } } @@ -50,5 +51,8 @@ dependencies { // implementation(libs.virtusize) implementation(project(":virtusize")) - implementation(libs.virtusize.auth) + implementation(libs.virtusize.auth) { + // TOOO: fix virtusize-auth dependency configuration + exclude(group = "com.virtusize.android", module = "virtusize-core") + } } diff --git a/sampleappjava/build.gradle.kts b/sampleappjava/build.gradle.kts index 7979f330..e02906a6 100644 --- a/sampleappjava/build.gradle.kts +++ b/sampleappjava/build.gradle.kts @@ -21,11 +21,12 @@ android { buildTypes { release { - isMinifyEnabled = false + isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro", ) + signingConfig = signingConfigs.getByName("debug") } } @@ -46,5 +47,8 @@ dependencies { // implementation(libs.virtusize) implementation(project(":virtusize")) - implementation(libs.virtusize.auth) + implementation(libs.virtusize.auth) { + // TOOO: fix virtusize-auth dependency configuration + exclude(group = "com.virtusize.android", module = "virtusize-core") + } } diff --git a/virtusize-core/build.gradle.kts b/virtusize-core/build.gradle.kts index c0c242c6..edbf4659 100644 --- a/virtusize-core/build.gradle.kts +++ b/virtusize-core/build.gradle.kts @@ -10,7 +10,7 @@ plugins { } android { - namespace = "com.virtusize.android.core" + namespace = "${Constants.GROUP_ID}.core" compileSdk = Constants.COMPILE_SDK defaultConfig { diff --git a/virtusize/build.gradle.kts b/virtusize/build.gradle.kts index ef0b7b84..9e2bdd96 100644 --- a/virtusize/build.gradle.kts +++ b/virtusize/build.gradle.kts @@ -9,7 +9,7 @@ plugins { } android { - namespace = "com.virtusize.android" + namespace = Constants.GROUP_ID compileSdk = Constants.COMPILE_SDK defaultConfig { @@ -64,7 +64,7 @@ android { dependencies { api(project(":virtusize-core")) - implementation(libs.virtusize.auth) + compileOnly(libs.virtusize.auth) implementation(libs.androidx.appcompat) implementation(libs.androidx.core.ktx) From cf002d64dbb446b163362a69aba96797fbcca8d9 Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sun, 4 Aug 2024 00:20:48 +0900 Subject: [PATCH 11/19] Revert the change to the JVM memory --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index f9d02186..20e2a015 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx4608m -Dfile.encoding=UTF-8 +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. For more details, visit # https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects From f95815ccc4cdcd70323b8ea2a13ac03b6eb54c5b Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sun, 4 Aug 2024 00:59:44 +0900 Subject: [PATCH 12/19] Bump virtusize-auth to 1.0.5 --- gradle/libs.versions.toml | 2 +- sampleAppKotlin/build.gradle.kts | 5 +---- sampleappjava/build.gradle.kts | 5 +---- virtusize/build.gradle.kts | 1 + virtusize/consumer-rules.pro | 0 5 files changed, 4 insertions(+), 9 deletions(-) create mode 100644 virtusize/consumer-rules.pro diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 79c07e78..5a93bd87 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ nextPublish = "1.1.0" robolectric = "4.13" truth = "1.4.4" virtusize = "2.5.5" -virtusizeAuth = "1.0.4" +virtusizeAuth = "1.0.5" [libraries] androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } diff --git a/sampleAppKotlin/build.gradle.kts b/sampleAppKotlin/build.gradle.kts index e452516c..8bb03423 100644 --- a/sampleAppKotlin/build.gradle.kts +++ b/sampleAppKotlin/build.gradle.kts @@ -51,8 +51,5 @@ dependencies { // implementation(libs.virtusize) implementation(project(":virtusize")) - implementation(libs.virtusize.auth) { - // TOOO: fix virtusize-auth dependency configuration - exclude(group = "com.virtusize.android", module = "virtusize-core") - } + implementation(libs.virtusize.auth) } diff --git a/sampleappjava/build.gradle.kts b/sampleappjava/build.gradle.kts index e02906a6..5afce26e 100644 --- a/sampleappjava/build.gradle.kts +++ b/sampleappjava/build.gradle.kts @@ -47,8 +47,5 @@ dependencies { // implementation(libs.virtusize) implementation(project(":virtusize")) - implementation(libs.virtusize.auth) { - // TOOO: fix virtusize-auth dependency configuration - exclude(group = "com.virtusize.android", module = "virtusize-core") - } + implementation(libs.virtusize.auth) } diff --git a/virtusize/build.gradle.kts b/virtusize/build.gradle.kts index 9e2bdd96..7d555eb7 100644 --- a/virtusize/build.gradle.kts +++ b/virtusize/build.gradle.kts @@ -16,6 +16,7 @@ android { minSdk = Constants.MIN_SDK testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") } buildTypes { diff --git a/virtusize/consumer-rules.pro b/virtusize/consumer-rules.pro new file mode 100644 index 00000000..e69de29b From 9557c9325a9dbd3a56e203a42c502467e099eabe Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sun, 4 Aug 2024 02:39:28 +0900 Subject: [PATCH 13/19] Create reusable functions to publish --- build.gradle.kts | 2 +- .../android/{ => constants}/Constants.kt | 2 +- .../{ => extensions}/ProjectExtensions.kt | 2 +- .../android/extensions/Publications.kt | 110 ++++++++++++++++++ sampleAppKotlin/build.gradle.kts | 2 +- sampleappjava/build.gradle.kts | 2 +- virtusize-core/build.gradle.kts | 80 ++----------- virtusize/build.gradle.kts | 80 ++----------- 8 files changed, 131 insertions(+), 149 deletions(-) rename buildSrc/src/main/java/com/virtusize/android/{ => constants}/Constants.kt (94%) rename buildSrc/src/main/java/com/virtusize/android/{ => extensions}/ProjectExtensions.kt (93%) create mode 100644 buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt diff --git a/build.gradle.kts b/build.gradle.kts index 82d6b9b9..9c1e427e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ -import com.virtusize.android.getProperties +import com.virtusize.android.extensions.getProperties // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { diff --git a/buildSrc/src/main/java/com/virtusize/android/Constants.kt b/buildSrc/src/main/java/com/virtusize/android/constants/Constants.kt similarity index 94% rename from buildSrc/src/main/java/com/virtusize/android/Constants.kt rename to buildSrc/src/main/java/com/virtusize/android/constants/Constants.kt index 293dccc7..e6913e32 100644 --- a/buildSrc/src/main/java/com/virtusize/android/Constants.kt +++ b/buildSrc/src/main/java/com/virtusize/android/constants/Constants.kt @@ -1,4 +1,4 @@ -package com.virtusize.android +package com.virtusize.android.constants object Constants { const val COMPILE_SDK = 34 diff --git a/buildSrc/src/main/java/com/virtusize/android/ProjectExtensions.kt b/buildSrc/src/main/java/com/virtusize/android/extensions/ProjectExtensions.kt similarity index 93% rename from buildSrc/src/main/java/com/virtusize/android/ProjectExtensions.kt rename to buildSrc/src/main/java/com/virtusize/android/extensions/ProjectExtensions.kt index 84eb9953..67f7c850 100644 --- a/buildSrc/src/main/java/com/virtusize/android/ProjectExtensions.kt +++ b/buildSrc/src/main/java/com/virtusize/android/extensions/ProjectExtensions.kt @@ -1,4 +1,4 @@ -package com.virtusize.android +package com.virtusize.android.extensions import org.gradle.api.Project import java.util.Properties diff --git a/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt b/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt new file mode 100644 index 00000000..41a218b4 --- /dev/null +++ b/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt @@ -0,0 +1,110 @@ +package com.virtusize.android.extensions + +import com.virtusize.android.constants.Constants +import org.gradle.api.Project +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.create +import org.gradle.kotlin.dsl.get +import org.gradle.plugins.signing.SigningExtension + +private fun Project.configureRepositories() { + configure { + publications { + repositories { + maven { + val releasesRepoUrl = + "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + val snapshotsRepoUrl = + "https://s01.oss.sonatype.org/content/repositories/snapshots/" + url = + uri(if (Constants.VERSION_NAME.endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) + credentials { + username = getProperties("OSSRH_USERNAME") + password = getProperties("OSSRH_PASSWORD") + } + } + } + } + } +} + +val mavenPublicationName = "maven" + +private fun Project.configureMavenPublication( + publishId: String, + publishName: String, + publishDescription: String, +) { + configure { + publications { + create(mavenPublicationName) { + groupId = Constants.GROUP_ID + artifactId = publishId + version = Constants.VERSION_NAME + + afterEvaluate { + from(components["release"]) + } + + pom { + name.set(publishName) + packaging = "aar" + description.set(publishDescription) + url.set(Constants.POM_URL) + + licenses { + license { + name.set("The MIT License") + url.set("https://raw.githubusercontent.com/virtusize/integration_android/master/LICENSE") + distribution.set("repo") + } + } + + developers { + developer { + id.set("virtusize") + name.set("Virtusize") + } + } + + scm { + connection.set("scm:git@github.com/virtusize/integration_android.git") + developerConnection.set("scm:git@github.com/virtusize/integration_android.git") + url.set(Constants.POM_URL) + } + } + } + } + } +} + +private fun Project.configureSigning(publication: PublishingExtension) { + configure { + val shouldSign = + getProperties("GPG_KEY_ID") != null && + getProperties("GPG_KEY") != null && + getProperties("GPG_KEY_PASSWORD") != null + isRequired = shouldSign && gradle.taskGraph.hasTask("publish") + if (isRequired) { + useInMemoryPgpKeys( + getProperties("GPG_KEY_ID"), + getProperties("GPG_KEY"), + getProperties("GPG_KEY_PASSWORD"), + ) + } + sign(publication.publications[mavenPublicationName]) + } +} + +fun Project.publish( + publishId: String, + publishName: String, + publishDescription: String, + publication: PublishingExtension, +) { + configureRepositories() + configureMavenPublication(publishId, publishName, publishDescription) + configureSigning(publication) +} diff --git a/sampleAppKotlin/build.gradle.kts b/sampleAppKotlin/build.gradle.kts index 8bb03423..b786fe70 100644 --- a/sampleAppKotlin/build.gradle.kts +++ b/sampleAppKotlin/build.gradle.kts @@ -1,4 +1,4 @@ -import com.virtusize.android.Constants +import com.virtusize.android.constants.Constants plugins { alias(libs.plugins.android.application) diff --git a/sampleappjava/build.gradle.kts b/sampleappjava/build.gradle.kts index 5afce26e..7639c13e 100644 --- a/sampleappjava/build.gradle.kts +++ b/sampleappjava/build.gradle.kts @@ -1,4 +1,4 @@ -import com.virtusize.android.Constants +import com.virtusize.android.constants.Constants plugins { alias(libs.plugins.android.application) diff --git a/virtusize-core/build.gradle.kts b/virtusize-core/build.gradle.kts index edbf4659..c9d612c3 100644 --- a/virtusize-core/build.gradle.kts +++ b/virtusize-core/build.gradle.kts @@ -1,5 +1,5 @@ -import com.virtusize.android.Constants -import com.virtusize.android.getProperties +import com.virtusize.android.constants.Constants +import com.virtusize.android.extensions.publish plugins { alias(libs.plugins.android.library) @@ -75,73 +75,9 @@ dependencies { testImplementation(libs.truth) } -publishing { - repositories { - maven { - val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" - val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" - url = uri(if (Constants.VERSION_NAME.endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) - credentials { - username = getProperties("OSSRH_USERNAME") - password = getProperties("OSSRH_PASSWORD") - } - } - } - - publications { - create("maven") { - groupId = Constants.GROUP_ID - artifactId = Constants.VirtusizeCore.ARTIFACT_ID - version = Constants.VERSION_NAME - - afterEvaluate { - from(components["release"]) - } - - pom { - name = Constants.VirtusizeCore.ARTIFACT_NAME - packaging = "aar" - description = Constants.VirtusizeCore.ARTIFACT_DESCRIPTION - url = Constants.POM_URL - - licenses { - license { - name = "The MIT License" - url = "https://raw.githubusercontent.com/virtusize/integration_android/master/LICENSE" - distribution = "repo" - } - } - - developers { - developer { - id = "virtusize" - name = "Virtusize" - } - } - - scm { - connection = "scm:git@github.com/virtusize/integration_android.git" - developerConnection = "scm:git@github.com/virtusize/integration_android.git" - url = Constants.POM_URL - } - } - } - } - - val shouldSign = - getProperties("GPG_KEY_ID") != null && - getProperties("GPG_KEY") != null && - getProperties("GPG_KEY_PASSWORD") != null - - signing { - isRequired = shouldSign && gradle.taskGraph.hasTask("publish") - if (isRequired) { - useInMemoryPgpKeys( - getProperties("GPG_KEY_ID"), - getProperties("GPG_KEY"), - getProperties("GPG_KEY_PASSWORD"), - ) - } - sign(publishing.publications["maven"]) - } -} +publish( + publishId = Constants.VirtusizeCore.ARTIFACT_ID, + publishName = Constants.VirtusizeCore.ARTIFACT_NAME, + publishDescription = Constants.VirtusizeCore.ARTIFACT_DESCRIPTION, + publication = publishing, +) diff --git a/virtusize/build.gradle.kts b/virtusize/build.gradle.kts index 7d555eb7..9d2b8ea0 100644 --- a/virtusize/build.gradle.kts +++ b/virtusize/build.gradle.kts @@ -1,5 +1,5 @@ -import com.virtusize.android.Constants -import com.virtusize.android.getProperties +import com.virtusize.android.constants.Constants +import com.virtusize.android.extensions.publish plugins { alias(libs.plugins.android.library) @@ -80,73 +80,9 @@ dependencies { testImplementation(libs.truth) } -publishing { - repositories { - maven { - val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" - val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" - url = uri(if (Constants.VERSION_NAME.endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) - credentials { - username = getProperties("OSSRH_USERNAME") - password = getProperties("OSSRH_PASSWORD") - } - } - } - - publications { - create("maven") { - groupId = Constants.GROUP_ID - artifactId = Constants.Virtusize.ARTIFACT_ID - version = Constants.VERSION_NAME - - afterEvaluate { - from(components["release"]) - } - - pom { - name = Constants.Virtusize.ARTIFACT_NAME - packaging = "aar" - description = Constants.Virtusize.ARTIFACT_DESCRIPTION - url = Constants.POM_URL - - licenses { - license { - name = "The MIT License" - url = "https://raw.githubusercontent.com/virtusize/integration_android/master/LICENSE" - distribution = "repo" - } - } - - developers { - developer { - id = "virtusize" - name = "Virtusize" - } - } - - scm { - connection = "scm:git@github.com/virtusize/integration_android.git" - developerConnection = "scm:git@github.com/virtusize/integration_android.git" - url = Constants.POM_URL - } - } - } - } - - val shouldSign = - getProperties("GPG_KEY_ID") != null && - getProperties("GPG_KEY") != null && - getProperties("GPG_KEY_PASSWORD") != null - - signing { - isRequired = shouldSign && gradle.taskGraph.hasTask("publish") - if (isRequired) { - useInMemoryPgpKeys( - getProperties("GPG_KEY_ID"), - getProperties("GPG_KEY"), - getProperties("GPG_KEY_PASSWORD"), - ) - } - sign(publishing.publications["maven"]) - } -} +publish( + publishId = Constants.Virtusize.ARTIFACT_ID, + publishName = Constants.Virtusize.ARTIFACT_NAME, + publishDescription = Constants.Virtusize.ARTIFACT_DESCRIPTION, + publication = publishing, +) From 5e8ee408eebeb7339378d609dd8d25eefe5faf2e Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sun, 4 Aug 2024 02:42:55 +0900 Subject: [PATCH 14/19] Remove constants --- .../com/virtusize/android/constants/Constants.kt | 13 ------------- .../virtusize/android/extensions/Publications.kt | 5 +++-- virtusize-core/build.gradle.kts | 6 +++--- virtusize/build.gradle.kts | 6 +++--- 4 files changed, 9 insertions(+), 21 deletions(-) diff --git a/buildSrc/src/main/java/com/virtusize/android/constants/Constants.kt b/buildSrc/src/main/java/com/virtusize/android/constants/Constants.kt index e6913e32..4bdb2e4f 100644 --- a/buildSrc/src/main/java/com/virtusize/android/constants/Constants.kt +++ b/buildSrc/src/main/java/com/virtusize/android/constants/Constants.kt @@ -8,17 +8,4 @@ object Constants { // Update versionName when publishing a new release const val VERSION_NAME = "2.5.5" const val GROUP_ID = "com.virtusize.android" - const val POM_URL = "https://github.com/virtusize/integration_android" - - object Virtusize { - const val ARTIFACT_ID = "virtusize" - const val ARTIFACT_NAME = "virtusize" - const val ARTIFACT_DESCRIPTION = "Virtusize Android SDK" - } - - object VirtusizeCore { - const val ARTIFACT_ID = "virtusize-core" - const val ARTIFACT_NAME = "virtusize-core" - const val ARTIFACT_DESCRIPTION = "The core module of Virtusize Android SDK" - } } diff --git a/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt b/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt index 41a218b4..c76b0157 100644 --- a/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt +++ b/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt @@ -49,10 +49,11 @@ private fun Project.configureMavenPublication( } pom { + val pomURL = "https://github.com/virtusize/integration_android" name.set(publishName) packaging = "aar" description.set(publishDescription) - url.set(Constants.POM_URL) + url.set(pomURL) licenses { license { @@ -72,7 +73,7 @@ private fun Project.configureMavenPublication( scm { connection.set("scm:git@github.com/virtusize/integration_android.git") developerConnection.set("scm:git@github.com/virtusize/integration_android.git") - url.set(Constants.POM_URL) + url.set(pomURL) } } } diff --git a/virtusize-core/build.gradle.kts b/virtusize-core/build.gradle.kts index c9d612c3..b42ccbd9 100644 --- a/virtusize-core/build.gradle.kts +++ b/virtusize-core/build.gradle.kts @@ -76,8 +76,8 @@ dependencies { } publish( - publishId = Constants.VirtusizeCore.ARTIFACT_ID, - publishName = Constants.VirtusizeCore.ARTIFACT_NAME, - publishDescription = Constants.VirtusizeCore.ARTIFACT_DESCRIPTION, + publishId = "virtusize-core", + publishName = "virtusize-core", + publishDescription = "The core module of Virtusize Android SDK", publication = publishing, ) diff --git a/virtusize/build.gradle.kts b/virtusize/build.gradle.kts index 9d2b8ea0..92a73e8b 100644 --- a/virtusize/build.gradle.kts +++ b/virtusize/build.gradle.kts @@ -81,8 +81,8 @@ dependencies { } publish( - publishId = Constants.Virtusize.ARTIFACT_ID, - publishName = Constants.Virtusize.ARTIFACT_NAME, - publishDescription = Constants.Virtusize.ARTIFACT_DESCRIPTION, + publishId = "virtusize", + publishName = "virtusize", + publishDescription = "Virtusize Android SDK", publication = publishing, ) From 6730cf4fff909bb8af3e6ed9922406a82369a71d Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sun, 4 Aug 2024 10:47:00 +0900 Subject: [PATCH 15/19] Update README.md --- README-JP.md | 73 ++++++++++++++++++++++++------------------ README.md | 89 +++++++++++++++++++++++++++++----------------------- 2 files changed, 92 insertions(+), 70 deletions(-) diff --git a/README-JP.md b/README-JP.md index 445492af..b05aaa63 100644 --- a/README-JP.md +++ b/README-JP.md @@ -68,15 +68,28 @@ You need a unique API key and an Admin account, only available to Virtusize cust もし、1.x.xの古いバージョンを引き続きご利用いただく場合は、[v1](https://github.com/virtusize/integration_android/tree/v1)を参照くださいませ。 + ### 1. Virtusize SDKを実装する In your appの`build.gradle`ファイルに下記のdependencyを追加 -```groovy -dependencies { - implementation 'com.virtusize.android:virtusize:2.5.5' -} -``` +- Groovy + + ```groovy + dependencies { + implementation 'com.virtusize.android:virtusize:2.5.5' + } + ``` + +- Kotlin + + ```kotlin + dependencies { + implementation("com.virtusize.android:virtusize:2.5.5") + } + ``` + + ### 2. Proguardの設定 @@ -109,12 +122,12 @@ Proguardをお使いの場合、Proguardのルールファイルに下記のル - Kotlin ```kotlin - lateinit var Virtusize: Virtusize + lateinit var virtusize: Virtusize override fun onCreate() { super.onCreate() // Initialize Virtusize instance for your application - Virtusize = VirtusizeBuilder().init(this) + virtusize = VirtusizeBuilder().init(this) // Only the API key is required .setApiKey("15cc36e1d7dad62b8e11722ce1a245cb6c5e6692") // For using the Order API, a user ID is required @@ -136,14 +149,14 @@ Proguardをお使いの場合、Proguardのルールファイルに下記のル - Java ```java - Virtusize Virtusize; + Virtusize virtusize; @Override public void onCreate() { super.onCreate(); // Initialize Virtusize instance for your application - Virtusize = new VirtusizeBuilder().init(this) + virtusize = new VirtusizeBuilder().init(this) // Only the API key is required .setApiKey("15cc36e1d7dad62b8e11722ce1a245cb6c5e6692") // For using the Order API, a user ID is required @@ -172,7 +185,7 @@ Proguardをお使いの場合、Proguardのルールファイルに下記のル - An `exernalId` that will be used to reference the product in the Virtusize server - An `imageURL` for the product image - - Pass the `VirtusizeProduct` object to the `Virtusize.load` function + - Pass the `VirtusizeProduct` object to the `Virtusize#load` function Kotlin @@ -183,7 +196,7 @@ val product = VirtusizeProduct( ) (application as App) - .Virtusize + .virtusize .load(product) ``` @@ -195,7 +208,7 @@ VirtusizeProduct product = new VirtusizeProduct( "http://www.image.com/goods/12345.jpg" ); -app.Virtusize.load(product); +app.virtusize.load(product); ``` @@ -257,7 +270,7 @@ Edit your `AndroidManifest.xml` file to include an intent filter and a `` override fun onCreate(savedInstanceState: Bundle?) { //... // Register message handler to listen to events from Virtusize - (application as App).Virtusize.registerMessageHandler(activityMessageHandler) + (application as App).virtusize.registerMessageHandler(activityMessageHandler) //... } ``` @@ -282,7 +295,7 @@ Edit your `AndroidManifest.xml` file to include an intent filter and a `` Log.e(TAG, error.getMessage()); } } - app.Virtusize.registerMessageHandler(virtusizeMessageHandler); + app.virtusize.registerMessageHandler(virtusizeMessageHandler); //... } ``` @@ -299,7 +312,7 @@ Message Handlerはアクティビティ(activity)やフラグメント(fra private val activityMessageHandler: VirtusizeMessageHandler override fun onPause() { // Always un register message handler in onPause() or depending on implementation onStop(). - (application as App).Virtusize.unregisterMessageHandler(activityMessageHandler) + (application as App).virtusize.unregisterMessageHandler(activityMessageHandler) super.onPause() } ``` @@ -310,7 +323,7 @@ Message Handlerはアクティビティ(activity)やフラグメント(fra VirtusizeMessageHandler virtusizeMessageHandler; @Override protected void onPause() { - app.Virtusize.unregisterMessageHandler(virtusizeMessageHandler); + app.virtusize.unregisterMessageHandler(virtusizeMessageHandler); super.onPause(); } ``` @@ -382,18 +395,18 @@ SDKのVirtusizeボタンには2つのデフォルトスタイルがあります android:text="@string/virtusize_button_text" /> ``` -**C. Connect the Virtusize button, along with the** `VirtusizeProduct` **object (which you have passed to** `Virtusize.load`) **into the Virtusize API by using the** `Virtusize.setupVirtusizeView` **function in your activity.** +**C. Connect the Virtusize button, along with the** `VirtusizeProduct` **object (which you have passed to** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** - Kotlin ```kotlin - (application as App).Virtusize.setupVirtusizeView(exampleVirtusizeButton, product) + (application as App).virtusize.setupVirtusizeView(exampleVirtusizeButton, product) ``` - Java ```java - app.Virtusize.setupVirtusizeView(virtusizeButton, product); + app.virtusize.setupVirtusizeView(virtusizeButton, product); ``` @@ -482,20 +495,20 @@ Virtusize SDKには2種類のInPageがあります。 virtusizeInPageStandard.setButtonTextSize(ExtensionsKt.getSpToPx(10)); ``` -- **Connect the InPage Standard, along with the** `VirtusizeProduct` **object (which you have passed to** `Virtusize.load`) **into the Virtusize API by using the** `Virtusize.setupVirtusizeView` **function in your activity.** +- **Connect the InPage Standard, along with the** `VirtusizeProduct` **object (which you have passed to** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** - Kotlin ```kotlin (application as App) - .Virtusize + .virtusize .setupVirtusizeView(exampleVirtusizeInPageStandard, product) ``` - Java ```java - app.Virtusize.setupVirtusizeView(virtusizeInPageStandard, product); + app.virtusize.setupVirtusizeView(virtusizeInPageStandard, product); ``` @@ -601,7 +614,7 @@ Virtusize SDKには2種類のInPageがあります。 virtusizeInPageMini.setButtonTextSize(ExtensionsKt.getSpToPx(10)); ``` -- **Connect the InPage Mini, along with the** `VirtusizeProduct` **object (which you have passed to** `Virtusize.load`) **into the Virtusize API by using the** `Virtusize.setupVirtusizeView` **function in your activity.** +- **Connect the InPage Mini, along with the** `VirtusizeProduct` **object (which you have passed to** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** - Kotlin @@ -614,7 +627,7 @@ Virtusize SDKには2種類のInPageがあります。 - Java ```java - app.Virtusize.setupVirtusizeView(virtusizeInPageMini, product); + app.vituriszesetupVirtusizeView(virtusizeInPageMini, product); ``` @@ -688,7 +701,7 @@ override fun onCreate() { } // アプリローンチ後に、アクティビティやフラグメントで設定 -(application as App).Virtusize.setUserID("user_id") +(application as App).vituriszesetUserID("user_id") ``` * Java @@ -708,14 +721,14 @@ Virtusize = new VirtusizeBuilder() // アプリローンチ後に、アクティビティやフラグメントで設定 app = (App) getApplication(); -app.Virtusize.setUserId("user_id"); +app.vituriszesetUserId("user_id"); ~~~~ #### 2. 注文データ向けに*VirtusizeOrder* オブジェクトを作成 -*VirtusizeOrder*オブジェクトはVirtusize.sendOrderに情報を送るもので、下記の項目が必要です。 +*VirtusizeOrder*オブジェクトはViturisze#sendOrderに情報を送るもので、下記の項目が必要です。 注意: * 表記のある場合項目は必須項目です @@ -789,7 +802,7 @@ items.add(new VirtusizeOrderItem( #### 3. 注文情報の送信 -ユーザーが注文完了時、ActivityあるいはFragment内で `Virtusize.sendOrder`を呼び出してください。 +ユーザーが注文完了時、ActivityあるいはFragment内で `Viturisze#sendOrder`を呼び出してください。 * Kotlin @@ -814,7 +827,7 @@ items.add(new VirtusizeOrderItem( `SuccessResponseHandler`と`ErrorResponseHandler`のコールバックはオプションです。 ~~~~java -app.Virtusize.sendOrder(order, +app.vituriszesendOrder(order, // This success callback is optional and gets called when the app successfully sends the order new SuccessResponseHandler() { @Override @@ -849,4 +862,4 @@ Use the [Virtusize Auth SDK](https://github.com/virtusize/virtusize_auth_android ## License -Copyright (c) 2018-21 Virtusize CO LTD (https://www.virtusize.jp) +Copyright (c) 2018-24 Virtusize CO LTD (https://www.virtusize.jp) diff --git a/README.md b/README.md index f829b381..f16597f0 100644 --- a/README.md +++ b/README.md @@ -68,15 +68,27 @@ You need a unique API key and an Admin account, only available to Virtusize cust If you'd like to continue using the old Version 1.x.x, refer to the branch [v1](https://github.com/virtusize/integration_android/tree/v1). + ### 1. Installation In your app `build.gradle` file, add the following dependencies: -```groovy -dependencies { - implementation 'com.virtusize.android:virtusize:2.5.5' -} -``` +- Groovy + + ```groovy + dependencies { + implementation 'com.virtusize.android:virtusize:2.5.5' + } + ``` + +- Kotlin + + ```kotlin + dependencies { + implementation("com.virtusize.android:virtusize:2.5.5") + } + ``` + ### 2. Proguard Rules @@ -110,12 +122,12 @@ Initialize the Virtusize object in your Application class's `onCreate` method us - Kotlin ```kotlin - lateinit var Virtusize: Virtusize + lateinit var virtusize: Virtusize override fun onCreate() { super.onCreate() // Initialize Virtusize instance for your application - Virtusize = VirtusizeBuilder().init(this) + virtusize = VirtusizeBuilder().init(this) // Only the API key is required .setApiKey("15cc36e1d7dad62b8e11722ce1a245cb6c5e6692") // For using the Order API, a user ID is required @@ -137,14 +149,14 @@ Initialize the Virtusize object in your Application class's `onCreate` method us - Java ```java - Virtusize Virtusize; + Virtusize virtusize; @Override public void onCreate() { super.onCreate(); // Initialize Virtusize instance for your application - Virtusize = new VirtusizeBuilder().init(this) + virtusize = new VirtusizeBuilder().init(this) // Only the API key is required .setApiKey("15cc36e1d7dad62b8e11722ce1a245cb6c5e6692") // For using the Order API, a user ID is required @@ -172,7 +184,7 @@ Initialize the Virtusize object in your Application class's `onCreate` method us - Create a `VirtusizeProduct` object with: - An `exernalId` that will be used to reference the product in the Virtusize server - An `imageURL` for the product image - - Pass the `VirtusizeProduct` object to the `Virtusize.load` function + - Pass the `VirtusizeProduct` object to the `Virtusize#load` function Kotlin @@ -184,9 +196,7 @@ Initialize the Virtusize object in your Application class's `onCreate` method us imageUrl = "http://www.image.com/goods/12345.jpg" ) - (application as App) - .Virtusize - .load(product) + (application as App).virtusize.load(product) ``` Java @@ -197,7 +207,7 @@ Initialize the Virtusize object in your Application class's `onCreate` method us "http://www.image.com/goods/12345.jpg" ); - app.Virtusize.load(product); + app.virtusize.load(product); ``` @@ -238,7 +248,6 @@ Edit your `AndroidManifest.xml` file to include an intent filter and a `` - ### 4. Register Virtusize Message Handler (Optional) Please do not forget to unregister message handler in activity or fragment's lifecycle method before it dies or is removed. See the next section for a how-to. @@ -259,7 +268,7 @@ Please do not forget to unregister message handler in activity or fragment's lif override fun onCreate(savedInstanceState: Bundle?) { //... // Register message handler to listen to events from Virtusize - (application as App).Virtusize.registerMessageHandler(activityMessageHandler) + (application as App).virtusize.registerMessageHandler(activityMessageHandler) //... } ``` @@ -284,7 +293,7 @@ Please do not forget to unregister message handler in activity or fragment's lif Log.e(TAG, error.getMessage()); } } - app.Virtusize.registerMessageHandler(virtusizeMessageHandler); + app.virtusize.registerMessageHandler(virtusizeMessageHandler); //... } ``` @@ -301,7 +310,7 @@ A message handler is tied to an activity or fragment's lifecycle, but the Virtus private val activityMessageHandler: VirtusizeMessageHandler override fun onPause() { // Always un register message handler in onPause() or depending on implementation onStop(). - (application as App).Virtusize.unregisterMessageHandler(activityMessageHandler) + (application as App).virtusize.unregisterMessageHandler(activityMessageHandler) super.onPause() } ``` @@ -312,7 +321,7 @@ A message handler is tied to an activity or fragment's lifecycle, but the Virtus VirtusizeMessageHandler virtusizeMessageHandler; @Override protected void onPause() { - app.Virtusize.unregisterMessageHandler(virtusizeMessageHandler); + app.virtusize.unregisterMessageHandler(virtusizeMessageHandler); super.onPause(); } ``` @@ -386,18 +395,18 @@ In order to use our default button styles, set `app:virtusizeButtonStyle="virtus android:text="@string/virtusize_button_text" /> ``` -**C. Connect the Virtusize button, along with the** `VirtusizeProduct` **object (which you have passed to ** `Virtusize.load`) **into the Virtusize API by using the** `Virtusize.setupVirtusizeView` **function in your activity.** +**C. Connect the Virtusize button, along with the** `VirtusizeProduct` **object (which you have passed to ** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** - Kotlin ```kotlin - (application as App).Virtusize.setupVirtusizeView(exampleVirtusizeButton, product) + (application as App).virtusize.setupVirtusizeView(exampleVirtusizeButton, product) ``` - Java ```java - app.Virtusize.setupVirtusizeView(virtusizeButton, product); + app.virtusize.setupVirtusizeView(virtusizeButton, product); ``` @@ -485,20 +494,20 @@ There are two types of InPage in our Virtusize SDK. virtusizeInPageStandard.setButtonTextSize(ExtensionsKt.getSpToPx(10)); ``` -- **Connect the InPage Standard, along with the** `VirtusizeProduct` **object (which you have passed to ** `Virtusize.load`) **into the Virtusize API by using the** `Virtusize.setupVirtusizeView` **function in your activity.** +- **Connect the InPage Standard, along with the** `VirtusizeProduct` **object (which you have passed to ** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** - Kotlin ```kotlin (application as App) - .Virtusize + .virtusize .setupVirtusizeView(exampleVirtusizeInPageStandard, product) ``` - Java ```java - app.Virtusize.setupVirtusizeView(virtusizeInPageStandard, product); + app.virtusize.setupVirtusizeView(virtusizeInPageStandard, product); ``` @@ -604,20 +613,20 @@ This is a mini version of InPage you can place in your application. The discreet virtusizeInPageMini.setButtonTextSize(ExtensionsKt.getSpToPx(10)); ``` -- **Connect the InPage Mini, along with the** `VirtusizeProduct` **object (which you have passed to ** `Virtusize.load`) **into the Virtusize API by using the** `Virtusize.setupVirtusizeView` **function in your activity.** +- **Connect the InPage Mini, along with the** `VirtusizeProduct` **object (which you have passed to ** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** - Kotlin ```kotlin (application as App) - .Virtusize + .virtusize .setupVirtusizeView(exampleVirtusizeInPageMini, product) ``` - Java ```java - app.Virtusize.setupVirtusizeView(virtusizeInPageMini, product); + app.virtusize.setupVirtusizeView(virtusizeInPageMini, product); ``` @@ -682,10 +691,10 @@ in your activity or fragment after the app is launched ```kotlin // In your Application class's `onCreate` method before the app is launched - lateinit var Virtusize: Virtusize + lateinit var virtusize: Virtusize override fun onCreate() { super.onCreate() - Virtusize = VirtusizeBuilder().init(this) + virtusize = VirtusizeBuilder().init(this) .setApiKey(api_key) .setUserId(user_id) .setEnv(VirtusizeEnvironment.STAGING) @@ -693,17 +702,17 @@ in your activity or fragment after the app is launched } // In your activity or fragment after the app is launched - (application as App).Virtusize.setUserID("user_id") + (application as App).virtusize.setUserID("user_id") ``` * Java ~~~~java // In your Application class's `onCreate` method before the app is launched - Virtusize Virtusize; + Virtusize virtusize; @Override public void onCreate() { super.onCreate(); - Virtusize = new VirtusizeBuilder() + virtusize = new VirtusizeBuilder() .init(this) .setApiKey(api_key) .setUserId(user_id) @@ -712,14 +721,14 @@ in your activity or fragment after the app is launched // In your activity or fragment after the app is launched app = (App) getApplication(); - app.Virtusize.setUserId("user_id"); + app.virtusize.setUserId("user_id"); ~~~~ #### 2. Create a *VirtusizeOrder* object for order data -The ***VirtusizeOrder*** object gets passed to the `Virtusize.sendOrder` method, and has the following attributes: +The ***VirtusizeOrder*** object gets passed to the `Virtusize#sendOrder` method, and has the following attributes: __**Note:**__ * means the attribute is required @@ -793,7 +802,7 @@ __**Note:**__ * means the attribute is required #### 3. Send an Order -Call the `Virtusize.sendOrder` method in your activity or fragment when the user places an order. +Call the `Virtusize#sendOrder` method in your activity or fragment when the user places an order. * Kotlin @@ -801,7 +810,7 @@ Call the `Virtusize.sendOrder` method in your activity or fragment when the user ~~~~kotlin (application as App) - .Virtusize + .virtusize .sendOrder(order, // This success callback is optional and gets called when the app successfully sends the order onSuccess = { @@ -819,7 +828,7 @@ Call the `Virtusize.sendOrder` method in your activity or fragment when the user The `SuccessResponseHandler` and `ErrorResponseHandler` callbacks are optional. ~~~~java - app.Virtusize.sendOrder(order, + app.virtusize.sendOrder(order, // This success callback is optional and gets called when the app successfully sends the order new SuccessResponseHandler() { @Override @@ -848,10 +857,10 @@ Use the [Virtusize Auth SDK](https://github.com/virtusize/virtusize_auth_android ## Examples 1. Kotlin example https://github.com/virtusize/integration_android/tree/master/sampleAppKotlin -2. Java example https://github.com/virtusize/integration_android/tree/master/sampleappjava +2. Java example https://github.com/virtusize/integration_android/tree/master/sampleAppJava ## License -Copyright (c) 2018-21 Virtusize CO LTD (https://www.virtusize.jp) \ No newline at end of file +Copyright (c) 2018-24 Virtusize CO LTD (https://www.virtusize.jp) \ No newline at end of file From 9df12f2ff7662aaf1908e979ec383ac3da2092d5 Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sun, 4 Aug 2024 11:08:10 +0900 Subject: [PATCH 16/19] Update README.md --- README-JP.md | 667 +++++++++++++++++++++++++-------------------------- README.md | 607 +++++++++++++++++++++++----------------------- 2 files changed, 632 insertions(+), 642 deletions(-) diff --git a/README-JP.md b/README-JP.md index b05aaa63..0c0fad57 100644 --- a/README-JP.md +++ b/README-JP.md @@ -4,18 +4,23 @@ [English](README.md) -Virtusize helps retailers to illustrate the size and fit of clothing, shoes and bags online, by letting customers compare the -measurements of an item they want to buy (on a retailer's product page) with an item that they already own (a reference item). -This is done by comparing the silhouettes of the retailer's product with the silhouette of the customer's reference Item. -Virtusize is a widget which opens when clicking on the Virtusize button, which is located next to the size selection on the product page. +Virtusize helps retailers to illustrate the size and fit of clothing, shoes and bags online, by +letting customers compare the +measurements of an item they want to buy (on a retailer's product page) with an item that they +already own (a reference item). +This is done by comparing the silhouettes of the retailer's product with the silhouette of the +customer's reference Item. +Virtusize is a widget which opens when clicking on the Virtusize button, which is located next to +the size selection on the product page. Read more about Virtusize at [https://www.virtusize.jp](https://www.virtusize.jp/) -You need a unique API key and an Admin account, only available to Virtusize customers. [Contact our sales team](mailto:sales@virtusize.com) to become a customer. - -> This is the integration script for native Android apps only. For web integration, refer to the developer documentation on https://developers.virtusize.com. For iOS integration, refer to https://github.com/virtusize/integration_ios - +You need a unique API key and an Admin account, only available to Virtusize +customers. [Contact our sales team](mailto:sales@virtusize.com) to become a customer. +> This is the integration script for native Android apps only. For web integration, refer to the +> developer documentation on https://developers.virtusize.com. For iOS integration, refer +> to https://github.com/virtusize/integration_ios ## Table of Contents @@ -23,29 +28,30 @@ You need a unique API key and an Admin account, only available to Virtusize cust - [はじめに](#はじめに) - - [Virtusize SDKを実装する](#1-virtusize-sdkを実装する) - - [Proguardの設定](#2-proguardの設定) + - [Virtusize SDKを実装する](#1-virtusize-sdkを実装する) + - [Proguardの設定](#2-proguardの設定) - [セットアップ](#セットアップ) - - [はじめに](#1-はじめに) - - [Load Product with Virtusize SDK](#2-load-product-with-virtusize-sdk) - - [Enable SNS Authentication](#3-enable-sns-authentication) - - [Virtusize Message Handlerの登録(オプション)](#4-virtusize-message-handlerの登録オプション) - - [Virtusize Message Handler登録解除(オプション)](#5-virtusize-message-handler登録解除オプション) + - [はじめに](#1-はじめに) + - [Load Product with Virtusize SDK](#2-load-product-with-virtusize-sdk) + - [Enable SNS Authentication](#3-enable-sns-authentication) + - [Virtusize Message Handlerの登録(オプション)](#4-virtusize-message-handlerの登録オプション) + - [Virtusize Message Handler登録解除(オプション)](#5-virtusize-message-handler登録解除オプション) - [Virtusize Views](#virtusize-views) - - [バーチャサイズボタン(Virtusize Button)](#1-バーチャサイズボタンvirtusize-button) - - [バーチャサイズ・インページ(Virtusize InPage)](#2-バーチャサイズインページvirtusize-inpage) - - [InPage Standard](#2-inpage-standard) - - [InPage Mini](#3-inpage-mini) + - [バーチャサイズボタン(Virtusize Button)](#1-バーチャサイズボタンvirtusize-button) + - [バーチャサイズ・インページ(Virtusize InPage)](#2-バーチャサイズインページvirtusize-inpage) + - [InPage Standard](#2-inpage-standard) + - [InPage Mini](#3-inpage-mini) - [Order APIについて](#order-apiについて) - - [初期化](#1-初期化) - - [注文データ向けに*VirtusizeOrder* オブジェクトを作成](#2-注文データ向けにvirtusizeorder-オブジェクトを作成) - - [注文情報の送信](#3-注文情報の送信) + - [初期化](#1-初期化) + - [注文データ向けに + *VirtusizeOrder* オブジェクトを作成](#2-注文データ向けにvirtusizeorder-オブジェクトを作成) + - [注文情報の送信](#3-注文情報の送信) - [Enable SNS Login in Virtusize for native Webview apps](#enable-sns-login-in-virtusize-for-native-webview-apps) @@ -53,27 +59,22 @@ You need a unique API key and an Admin account, only available to Virtusize cust - [License](#license) - - ## 対応バージョン - minSdkVersion >= 21 - compileSdkVersion >= 34 - Setup in AppCompatActivity - - ## はじめに -もし、1.x.xの古いバージョンを引き続きご利用いただく場合は、[v1](https://github.com/virtusize/integration_android/tree/v1)を参照くださいませ。 - - +もし、1.x.xの古いバージョンを引き続きご利用いただく場合は、[v1](https://github.com/virtusize/integration_android/tree/v1) +を参照くださいませ。 ### 1. Virtusize SDKを実装する In your appの`build.gradle`ファイルに下記のdependencyを追加 -- Groovy +- Groovy (build.gradle) ```groovy dependencies { @@ -81,7 +82,7 @@ In your appの`build.gradle`ファイルに下記のdependencyを追加 } ``` -- Kotlin +- Kotlin (build.gradle.kts) ```kotlin dependencies { @@ -89,8 +90,6 @@ In your appの`build.gradle`ファイルに下記のdependencyを追加 } ``` - - ### 2. Proguardの設定 Proguardをお使いの場合、Proguardのルールファイルに下記のルールを追加 @@ -99,25 +98,24 @@ Proguardをお使いの場合、Proguardのルールファイルに下記のル -keep class com.virtusize.android.** ``` - - ## セットアップ ### 1. はじめに -アプリケーションクラスの`onCreate`メソッドでVirtusizeオブジェクトを**VirtusizeBuilder**を使って初期化し、設定を行います。可能な設定方法を以下の表に示します。 +アプリケーションクラスの`onCreate`メソッドでVirtusizeオブジェクトを**VirtusizeBuilder** +を使って初期化し、設定を行います。可能な設定方法を以下の表に示します。 **VirtusizeBuilder** -| 項目 | データ形式 | 例 | 説明 | 要件 | -| -------------------- | ----------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | -| setApiKey | String | setApiKey("api_key") | 固有のAPIキーは各Virtusizeクライアントに提供されます。 | あり。 | -| setUserId | String | setUserId("123") | ユーザーがクライアントのアプリにログインしている場合に、クライアントから渡されます。 | あり。Order APIを使用する場合。 | -| setEnv | VirtusizeEnvironment | setEnv(VirtusizeEnvironment.STAGING) | 環境は実装をしている環境を選択してください、`VirtusizeEnvironment.STAGING`, `VirtusizeEnvironment.GLOBAL`, `VirtusizeEnvironment.JAPAN` or `VirtusizeEnvironment.KOREA`のいずれかです。 | 特になし。デフォルトでは、`VirtusizeEnvironment.GLOBAL`に設定されます。 | -| setLanguage | VirtusizeLanguage | setLanguage(VirtusizeLanguage.EN) | インテグレーションをロードする際の初期言語を設定します。設定可能な値は以下:`VirtusizeLanguage.EN`, `VirtusizeLanguage.JP` および`VirtusizeLanguage.KR` | 特になし。デフォルトでは、初期言語はVirtusizeの環境に基づいて設定されます。 | -| setShowSGI | Boolean | setShowSGI(true) | ユーザーが生成したアイテムをワードローブに追加するために、SGIを取得してSGIフローを使用するかどうかを決定します。 | 特になし。デフォルトではShowSGIはfalseに設定されています。 | -| setAllowedLanguages | `VirtusizeLanguage`列挙のリスト | In Kotlin, setAllowedLanguages(mutableListOf(VirtusizeLanguage.EN, VirtusizeLanguage.JP))
In Java, setAllowedLanguages(Arrays.asList(VirtusizeLanguage.EN, VirtusizeLanguage.JP)) | ユーザーが言語選択ボタンより選択できる言語 | 特になし。デフォルトでは、英語、日本語、韓国語など、表示可能なすべての言語が表示されるようになっています。 | -| setDetailsPanelCards | `VirtusizeInfoCategory`列挙のリスト | In Kotlin, setDetailsPanelCards(mutableListOf(VirtusizeInfoCategory.BRAND_SIZING, VirtusizeInfoCategory.GENERAL_FIT))
In Java, setDetailsPanelCards(Arrays.asList(VirtusizeInfoCategory.BRAND_SIZING, VirtusizeInfoCategory.GENERAL_FIT)) | 商品詳細タブに表示する情報のカテゴリ。表示可能カテゴリは以下:`VirtusizeInfoCategory.MODELINFO`, `VirtusizeInfoCategory.GENERALFIT`, `VirtusizeInfoCategory.BRANDSIZING` および `VirtusizeInfoCategory.MATERIAL` | 特になし。デフォルトでは、商品詳細タブに表示可能なすべての情報カテゴリが表示されます。 | +| 項目 | データ形式 | 例 | 説明 | 要件 | +|----------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------| +| setApiKey | String | setApiKey("api_key") | 固有のAPIキーは各Virtusizeクライアントに提供されます。 | あり。 | +| setUserId | String | setUserId("123") | ユーザーがクライアントのアプリにログインしている場合に、クライアントから渡されます。 | あり。Order APIを使用する場合。 | +| setEnv | VirtusizeEnvironment | setEnv(VirtusizeEnvironment.STAGING) | 環境は実装をしている環境を選択してください、`VirtusizeEnvironment.STAGING`, `VirtusizeEnvironment.GLOBAL`, `VirtusizeEnvironment.JAPAN` or `VirtusizeEnvironment.KOREA`のいずれかです。 | 特になし。デフォルトでは、`VirtusizeEnvironment.GLOBAL`に設定されます。 | +| setLanguage | VirtusizeLanguage | setLanguage(VirtusizeLanguage.EN) | インテグレーションをロードする際の初期言語を設定します。設定可能な値は以下:`VirtusizeLanguage.EN`, `VirtusizeLanguage.JP` および`VirtusizeLanguage.KR` | 特になし。デフォルトでは、初期言語はVirtusizeの環境に基づいて設定されます。 | +| setShowSGI | Boolean | setShowSGI(true) | ユーザーが生成したアイテムをワードローブに追加するために、SGIを取得してSGIフローを使用するかどうかを決定します。 | 特になし。デフォルトではShowSGIはfalseに設定されています。 | +| setAllowedLanguages | `VirtusizeLanguage`列挙のリスト | In Kotlin, setAllowedLanguages(mutableListOf(VirtusizeLanguage.EN, VirtusizeLanguage.JP))
In Java, setAllowedLanguages(Arrays.asList(VirtusizeLanguage.EN, VirtusizeLanguage.JP)) | ユーザーが言語選択ボタンより選択できる言語 | 特になし。デフォルトでは、英語、日本語、韓国語など、表示可能なすべての言語が表示されるようになっています。 | +| setDetailsPanelCards | `VirtusizeInfoCategory`列挙のリスト | In Kotlin, setDetailsPanelCards(mutableListOf(VirtusizeInfoCategory.BRAND_SIZING, VirtusizeInfoCategory.GENERAL_FIT))
In Java, setDetailsPanelCards(Arrays.asList(VirtusizeInfoCategory.BRAND_SIZING, VirtusizeInfoCategory.GENERAL_FIT)) | 商品詳細タブに表示する情報のカテゴリ。表示可能カテゴリは以下:`VirtusizeInfoCategory.MODELINFO`, `VirtusizeInfoCategory.GENERALFIT`, `VirtusizeInfoCategory.BRANDSIZING` および `VirtusizeInfoCategory.MATERIAL` | 特になし。デフォルトでは、商品詳細タブに表示可能なすべての情報カテゴリが表示されます。 | - Kotlin @@ -175,67 +173,66 @@ Proguardをお使いの場合、Proguardのルールファイルに下記のル } ``` - - ### 2. Load Product with Virtusize SDK 1. アクティビティ内では、 - - Create a `VirtusizeProduct` object with: - - An `exernalId` that will be used to reference the product in the Virtusize server - - An `imageURL` for the product image - - - Pass the `VirtusizeProduct` object to the `Virtusize#load` function + - Create a `VirtusizeProduct` object with: + +- An `exernalId` that will be used to reference the product in the Virtusize server + - An `imageURL` for the product image - Kotlin + - Pass the `VirtusizeProduct` object to the `Virtusize#load` function + +Kotlin ```kotlin val product = VirtusizeProduct( externalId = "vs_dress", imageUrl = "http://www.image.com/goods/12345.jpg" ) - + (application as App) - .virtusize - .load(product) + .virtusize + .load(product) ``` - Java +Java ```java VirtusizeProduct product = new VirtusizeProduct( - "vs_dress", - "http://www.image.com/goods/12345.jpg" + "vs_dress", + "http://www.image.com/goods/12345.jpg" ); -app.virtusize.load(product); - ``` - +app.virtusize. +load(product); + ``` ### 3. Enable SNS authentication -The SNS authentication flow requires opening a Chrome Custom Tab, which will load a web page for the user to login with their SNS account. A custom URL scheme must be defined to return the login response to your app from a Chrome Custom Tab. +The SNS authentication flow requires opening a Chrome Custom Tab, which will load a web page for the +user to login with their SNS account. A custom URL scheme must be defined to return the login +response to your app from a Chrome Custom Tab. -Edit your `AndroidManifest.xml` file to include an intent filter and a `` tag for the custom URL scheme. +Edit your `AndroidManifest.xml` file to include an intent filter and a `` tag for the custom +URL scheme. ```xml + - - + package="com.your-company.your-app"> + + - + - - + + @@ -245,10 +242,8 @@ Edit your `AndroidManifest.xml` file to include an intent filter and a `` **❗IMPORTANT** 1. The URL host has to be `sns-auth` -2. The URL scheme must begin with your app's package ID (com.your-company.your-app) and **end with .virtusize**, and the scheme which you define must use all **lowercase** letters. - - - +2. The URL scheme must begin with your app's package ID (com.your-company.your-app) and **end with + .virtusize**, and the scheme which you define must use all **lowercase** letters. ### 4. Virtusize Message Handlerの登録(オプション) @@ -300,11 +295,13 @@ Edit your `AndroidManifest.xml` file to include an intent filter and a `` } ``` - - ### 5. Virtusize Message Handler登録解除(オプション) -Message Handlerはアクティビティ(activity)やフラグメント(fragment)のライフサイクル(lifecycle)に結びついていますが、Virtusizeライブラリオブジェクトはアプリケーションのライフサイクルに結びついています。そのため、Message Handlerの登録解除を忘れると、アクティビティが終了したりフラグメントが削除されたりしても、イベントを聞き続けることになります。アクティビティの場合、ライフサイクルのどこでMessage Handlerを登録したかによって、superメソッドが呼ばれる前に`onPause`または`onStop`メソッドで登録を解除する必要があります。フラグメントの場合も、同様のガイドラインに従ってください。 +Message +Handlerはアクティビティ(activity)やフラグメント(fragment)のライフサイクル(lifecycle)に結びついていますが、Virtusizeライブラリオブジェクトはアプリケーションのライフサイクルに結びついています。そのため、Message +Handlerの登録解除を忘れると、アクティビティが終了したりフラグメントが削除されたりしても、イベントを聞き続けることになります。アクティビティの場合、ライフサイクルのどこでMessage +Handlerを登録したかによって、superメソッドが呼ばれる前に`onPause`または`onStop` +メソッドで登録を解除する必要があります。フラグメントの場合も、同様のガイドラインに従ってください。 - Kotlin @@ -328,11 +325,10 @@ Message Handlerはアクティビティ(activity)やフラグメント(fra } ``` - - ## Virtusize Views -SDKをセットアップした後、`VirtusizeView`を追加して、顧客が理想的なサイズを見つけられるようにします。Virtusize SDKはユーザーが使用するために2つの主要なUIコンポーネントを提供します。: +SDKをセットアップした後、`VirtusizeView`を追加して、顧客が理想的なサイズを見つけられるようにします。Virtusize +SDKはユーザーが使用するために2つの主要なUIコンポーネントを提供します。: ### 1. バーチャサイズボタン(Virtusize Button) @@ -340,25 +336,22 @@ SDKをセットアップした後、`VirtusizeView`を追加して、顧客が VirtusizeButtonはこのSDKの中でもっとシンプルなUIのボタンです。ユーザーが正しいサイズを見つけられるように、ウェブビューでアプリケーションを開きます。 - - #### (2) デフォルトスタイル SDKのVirtusizeボタンには2つのデフォルトスタイルがあります。 -| Teal Theme | Black Theme | -| :----------------------------------------------------------: | :----------------------------------------------------------: | +| Teal Theme | Black Theme | +|:-----------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------:| | | | もしご希望であれば、ボタンのスタイルもカスタマイズすることができます。 - - #### (3) 使用方法 -**A. アクティビティのXMLレイアウトファイルにVirtusizeButtonを追加してください。** +**A. アクティビティのXMLレイアウトファイルにVirtusizeButtonを追加してください。** -私たちのデフォルトのボタンスタイルを使用するために、XMLで`app:virtusizeButtonStyle="virtusize_black "`または`app:virtusizeButtonStyle="virtusize_teal "`を設定します。 +私たちのデフォルトのボタンスタイルを使用するために、XMLで`app:virtusizeButtonStyle="virtusize_black "` +または`app:virtusizeButtonStyle="virtusize_teal "`を設定します。 - XML @@ -384,18 +377,19 @@ SDKのVirtusizeボタンには2つのデフォルトスタイルがあります virtusizeButton.setVirtusizeViewStyle(VirtusizeViewStyle.TEAL); ``` -**B. また、他のボタンスタイルを使用したり、ボタンの属性(テキスト、高さ、幅など)を定義することもできます。** +**B. また、他のボタンスタイルを使用したり、ボタンの属性(テキスト、高さ、幅など)を定義することもできます。 +** ```xml - + + ``` -**C. Connect the Virtusize button, along with the** `VirtusizeProduct` **object (which you have passed to** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** +**C. Connect the Virtusize button, along with the** `VirtusizeProduct` **object (which you have +passed to** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` +**function in your activity.** - Kotlin @@ -409,8 +403,6 @@ SDKのVirtusizeボタンには2つのデフォルトスタイルがあります app.virtusize.setupVirtusizeView(virtusizeButton, product); ``` - - ### 2. バーチャサイズ・インページ(Virtusize InPage) #### (1) はじめに @@ -421,8 +413,8 @@ Virtusize InPageは、私たちのサービスのスタートボタンのよう Virtusize SDKには2種類のInPageがあります。 -| InPage Standard | InPage Mini | -| :----------------------------------------------------------: | :----------------------------------------------------------: | +| InPage Standard | InPage Mini | +|:----------------------------------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------------------------------:| | ![InPageStandard](https://user-images.githubusercontent.com/7802052/92671977-9cb1fe80-f352-11ea-803b-5e3cb3469be4.png) | ![InPageMini](https://user-images.githubusercontent.com/7802052/92671979-9e7bc200-f352-11ea-8594-ed441649855c.png) | ⚠️**注意事項**⚠️ @@ -431,87 +423,91 @@ Virtusize SDKには2種類のInPageがあります。 2. InPage Miniは、必ずInPage Standardと組み合わせてご利用ください。 - - #### (2) InPage Standard -##### A. 使用方法 +##### A. 使用方法 - **アクティビティのXMLレイアウトファイルにVirtusizeInPageStandを追加します。** - 1. 私たちのデフォルトスタイルを使用するために、 `app:virtusizeInPageStandardStyle="virtusize_black"` または `app:virtusizeInPageStandardStyle="virtusize_teal"` を設定してください。 - - 2. CTAボタンの背景色を変更したい場合は、`app:inPageStandardButtonBackgroundColor="#123456 "`を使用できます。 - - 3. アプリ画面の端とInPageStandardの間の水平方向の余白を設定したい場合は、`app:inPageStandardHorizontalMargin="16dp"`とします。 - - 4. InPageStandardのフォントサイズを変更したい場合は、 `app:inPageStandardMessageTextSize="10sp"` と`app:inPageStandardButtonTextSize="10sp"`を利用できます。 - - - XML - - ```xml - - ``` - - ```xml - - ``` - - または、プログラムとして設定します。 - - - Kotlin - - ```kotlin - // Set the Virtusize view style - exampleVirtusizeInPageStandard.virtusizeViewStyle = VirtusizeViewStyle.TEAL - // Set the horizontal margins between the edges of the app screen and the InPageStandard - // Note: Use the helper extension function `dpInPx` if you like - exampleVirtusizeInPageStandard.horizontalMargin = 16.dpInPx - // Set the background color of the check size button in InPage Standard - exampleVirtusizeInPageStandard.setButtonBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)) - // Set the text sizes of the InPage message and the check size button - exampleVirtusizeInPageStandard.messageTextSize = 10f.spToPx - exampleVirtusizeInPageStandard.buttonTextSize = 10f.spToPx - ``` - - - Java - - ```java - virtusizeInPageStandard.setVirtusizeViewStyle(VirtusizeViewStyle.BLACK); - virtusizeInPageStandard.setHorizontalMargin(ExtensionsKt.getDpInPx(16)); - virtusizeInPageStandard.setButtonBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)); - virtusizeInPageStandard.setMessageTextSize(ExtensionsKt.getSpToPx(10)); - virtusizeInPageStandard.setButtonTextSize(ExtensionsKt.getSpToPx(10)); - ``` - -- **Connect the InPage Standard, along with the** `VirtusizeProduct` **object (which you have passed to** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** - - - Kotlin - - ```kotlin - (application as App) - .virtusize - .setupVirtusizeView(exampleVirtusizeInPageStandard, product) - ``` - - - Java - - ```java - app.virtusize.setupVirtusizeView(virtusizeInPageStandard, product); - ``` - - + 1. + 私たちのデフォルトスタイルを使用するために、 `app:virtusizeInPageStandardStyle="virtusize_black"` + または `app:virtusizeInPageStandardStyle="virtusize_teal"` を設定してください。 + + 2. CTAボタンの背景色を変更したい場合は、`app:inPageStandardButtonBackgroundColor="#123456 "` + を使用できます。 + + 3. + アプリ画面の端とInPageStandardの間の水平方向の余白を設定したい場合は、`app:inPageStandardHorizontalMargin="16dp"` + とします。 + + 4. InPageStandardのフォントサイズを変更したい場合は、 `app:inPageStandardMessageTextSize="10sp"` + と`app:inPageStandardButtonTextSize="10sp"`を利用できます。 + + - XML + + ```xml + + ``` + + ```xml + + ``` + + または、プログラムとして設定します。 + + - Kotlin + + ```kotlin + // Set the Virtusize view style + exampleVirtusizeInPageStandard.virtusizeViewStyle = VirtusizeViewStyle.TEAL + // Set the horizontal margins between the edges of the app screen and the InPageStandard + // Note: Use the helper extension function `dpInPx` if you like + exampleVirtusizeInPageStandard.horizontalMargin = 16.dpInPx + // Set the background color of the check size button in InPage Standard + exampleVirtusizeInPageStandard.setButtonBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)) + // Set the text sizes of the InPage message and the check size button + exampleVirtusizeInPageStandard.messageTextSize = 10f.spToPx + exampleVirtusizeInPageStandard.buttonTextSize = 10f.spToPx + ``` + + - Java + + ```java + virtusizeInPageStandard.setVirtusizeViewStyle(VirtusizeViewStyle.BLACK); + virtusizeInPageStandard.setHorizontalMargin(ExtensionsKt.getDpInPx(16)); + virtusizeInPageStandard.setButtonBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)); + virtusizeInPageStandard.setMessageTextSize(ExtensionsKt.getSpToPx(10)); + virtusizeInPageStandard.setButtonTextSize(ExtensionsKt.getSpToPx(10)); + ``` + +- **Connect the InPage Standard, along with the** `VirtusizeProduct` **object (which you have passed + to** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` * + *function in your activity.** + + - Kotlin + + ```kotlin + (application as App) + .virtusize + .setupVirtusizeView(exampleVirtusizeInPageStandard, product) + ``` + + - Java + + ```java + app.virtusize.setupVirtusizeView(virtusizeInPageStandard, product); + ``` ##### B. デザインガイドライン @@ -520,7 +516,7 @@ Virtusize SDKには2種類のInPageがあります。 デフォルトデザインは2種類あります。 | Teal Theme | Black Theme | - | :----------------------------------------------------------: | :----------------------------------------------------------: | + | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![InPageStandardTeal](https://user-images.githubusercontent.com/7802052/92672035-b9e6cd00-f352-11ea-9e9e-5385a19e96da.png) | ![InPageStandardBlack](https://user-images.githubusercontent.com/7802052/92672031-b81d0980-f352-11ea-8b7a-564dd6c2a7f1.png) | - ##### レイアウトのバリエーション @@ -528,34 +524,35 @@ Virtusize SDKには2種類のInPageがあります。 設定可能なレイアウト例 | 1 thumbnail + 2 lines of message | 2 thumbnails + 2 lines of message | - | :----------------------------------------------------------: | :----------------------------------------------------------: | + | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![1 thumbnail + 2 lines of message](https://user-images.githubusercontent.com/7802052/97399368-5e879300-1930-11eb-8b77-b49e06813550.png) | ![2 thumbnails + 2 lines of message](https://user-images.githubusercontent.com/7802052/97399370-5f202980-1930-11eb-9a2d-7b71714aa7b4.png) | | **1 thumbnail + 1 line of message** | **2 animated thumbnails + 2 lines of message** | | ![1 thumbnail + 1 line of message](https://user-images.githubusercontent.com/7802052/97399373-5f202980-1930-11eb-81fe-9946b656eb4c.png) | ![2 animated thumbnails + 2 lines of message](https://user-images.githubusercontent.com/7802052/97399355-59c2df00-1930-11eb-8a52-292956b8762d.gif) | - ##### 推奨設定箇所 - - サイズテーブルの近く + - サイズテーブルの近く - - サイズ情報掲載箇所 + - サイズ情報掲載箇所 - ##### UI カスタマイゼーション - - **変更可:** - - CTAボタンの背景色([WebAIM contrast test](https://webaim.org/resources/contrastchecker/)で問題がなければ) - - Inpageの横幅(アプリの横幅に合わせて変更可) + - **変更可:** + - CTAボタンの背景色([WebAIM contrast test](https://webaim.org/resources/contrastchecker/) + で問題がなければ) + - Inpageの横幅(アプリの横幅に合わせて変更可) - - **変更不可**: - - 形状やスペースなどのインターフェイスコンポーネント - - フォント - - CTA ボタンの形状 - - テキスト文言 - - ボタンシャドウ(削除も不可) - - VIRTUSIZE ロゴと プライバシーポリシーのテキストが入ったフッター(削除も不可) + - **変更不可**: + - 形状やスペースなどのインターフェイスコンポーネント + - フォント + - CTA ボタンの形状 + - テキスト文言 + - ボタンシャドウ(削除も不可) + - VIRTUSIZE ロゴと プライバシーポリシーのテキストが入ったフッター(削除も不可) -##### +##### #### (3) InPage Mini @@ -565,72 +562,74 @@ Virtusize SDKには2種類のInPageがあります。 - **アクティビティのXMLレイアウトファイルにVirtusizeInPageMiniを追加します。** - 1. 私たちのデフォルトスタイルを使用するには、`app:virtusizeInPageMiniStyle="virtusize_black"` または `app:virtusizeInPageMiniStyle="virtusize_teal"` を設定してください。 - - 2. バーの背景色を変更したい場合は、`app:inPageMiniBackgroundColor="#123456"` とします。 - - 3. InPage Miniのフォントサイズを変更したい場合は、 `app:inPageMiniMessageTextSize="12sp"` と`app:inPageMiniButtonTextSize="10sp"`を利用できます。 + 1. 私たちのデフォルトスタイルを使用するには、`app:virtusizeInPageMiniStyle="virtusize_black"` + または `app:virtusizeInPageMiniStyle="virtusize_teal"` を設定してください。 - - XML + 2. バーの背景色を変更したい場合は、`app:inPageMiniBackgroundColor="#123456"` とします。 - ```xml - - ``` + 3. InPage Miniのフォントサイズを変更したい場合は、 `app:inPageMiniMessageTextSize="12sp"` + と`app:inPageMiniButtonTextSize="10sp"`を利用できます。 - ```xml - - ``` + - XML - もしくは、プログラムとして設定します。 + ```xml + + ``` - - Kotlin + ```xml + + ``` - ```kotlin - // Set the Virtusize view style - exampleVirtusizeInPageMini.virtusizeViewStyle = VirtusizeViewStyle.BLACK - // Set the background color of the InPageMini view - exampleVirtusizeInPageMini.setInPageMiniBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)) - // Set the text sizes of the InPage message and the check size button - exampleVirtusizeInPageMini.messageTextSize = 12f.spToPx - exampleVirtusizeInPageMini.buttonTextSize = 10f.spToPx - ``` + もしくは、プログラムとして設定します。 - - Java + - Kotlin - ```java - virtusizeInPageMini.setVirtusizeViewStyle(VirtusizeViewStyle.TEAL); - virtusizeInPageMini.setInPageMiniBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)); - virtusizeInPageMini.setMessageTextSize(ExtensionsKt.getSpToPx(12)); - virtusizeInPageMini.setButtonTextSize(ExtensionsKt.getSpToPx(10)); - ``` + ```kotlin + // Set the Virtusize view style + exampleVirtusizeInPageMini.virtusizeViewStyle = VirtusizeViewStyle.BLACK + // Set the background color of the InPageMini view + exampleVirtusizeInPageMini.setInPageMiniBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)) + // Set the text sizes of the InPage message and the check size button + exampleVirtusizeInPageMini.messageTextSize = 12f.spToPx + exampleVirtusizeInPageMini.buttonTextSize = 10f.spToPx + ``` -- **Connect the InPage Mini, along with the** `VirtusizeProduct` **object (which you have passed to** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** + - Java - - Kotlin + ```java + virtusizeInPageMini.setVirtusizeViewStyle(VirtusizeViewStyle.TEAL); + virtusizeInPageMini.setInPageMiniBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)); + virtusizeInPageMini.setMessageTextSize(ExtensionsKt.getSpToPx(12)); + virtusizeInPageMini.setButtonTextSize(ExtensionsKt.getSpToPx(10)); + ``` - ```kotlin - (application as App) - .Virtusize - .setupVirtusizeView(exampleVirtusizeInPageMini, product) - ``` +- **Connect the InPage Mini, along with the** `VirtusizeProduct` **object (which you have passed to + ** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` * + *function in your activity.** - - Java + - Kotlin - ```java - app.vituriszesetupVirtusizeView(virtusizeInPageMini, product); - ``` + ```kotlin + (application as App) + .Virtusize + .setupVirtusizeView(exampleVirtusizeInPageMini, product) + ``` + - Java + ```java + app.vituriszesetupVirtusizeView(virtusizeInPageMini, product); + ``` ##### B. デザインガイドライン @@ -639,52 +638,53 @@ Virtusize SDKには2種類のInPageがあります。 2種類のでフォルトデザインを用意しています。 | Teal Theme | Black Theme | - | :----------------------------------------------------------: | :----------------------------------------------------------: | + | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![InPageMiniTeal](https://user-images.githubusercontent.com/7802052/92672234-2d88da00-f353-11ea-99d9-b9e9b6aa5620.png) | ![InPageMiniBlack](https://user-images.githubusercontent.com/7802052/92672232-2c57ad00-f353-11ea-80f6-55a9c72fb0b5.png) | - ##### 推奨設置箇所 | Underneath the product image | Underneath or near the size table | - | :----------------------------------------------------------: | :----------------------------------------------------------: | + | :----------------------------------------------------------: | :----------------------------------------------------------: | | | | - ##### デフォルトのフォント - - **Japanese** - - Noto Sans CJK JP - - 12sp (メッセージ文言) - - 10sp (ボタン内テキスト) - - **Noto Sans CJK JP** - - Noto Sans CJK KR - - 12sp (メッセージ文言) - - 10sp (ボタン内テキスト) - - **Noto Sans CJK JP** - - Roboto - - 14sp (メッセージ文言) - - 12sp (ボタン内テキスト) + - **Japanese** + - Noto Sans CJK JP + - 12sp (メッセージ文言) + - 10sp (ボタン内テキスト) + - **Noto Sans CJK JP** + - Noto Sans CJK KR + - 12sp (メッセージ文言) + - 10sp (ボタン内テキスト) + - **Noto Sans CJK JP** + - Roboto + - 14sp (メッセージ文言) + - 12sp (ボタン内テキスト) - ##### UI カスタマイゼーション - - **変更可** - - CTAボタンの背景色([WebAIM contrast test](https://webaim.org/resources/contrastchecker/)で問題がなければ) - - **変更不可:** - - フォント - - CTA ボタンの形状 - - テキスト文言 - - + - **変更可** + - CTAボタンの背景色([WebAIM contrast test](https://webaim.org/resources/contrastchecker/) + で問題がなければ) + - **変更不可:** + - フォント + - CTA ボタンの形状 + - テキスト文言 ## Order APIについて -The order APIはバーチャサイズがユーザーが購入した商品を`Purchase History`(購入履歴)の一部として表示するために必要で、これらの商品がユーザーが購入検討している商品と比較可能になります。 +The order APIはバーチャサイズがユーザーが購入した商品を`Purchase History` +(購入履歴)の一部として表示するために必要で、これらの商品がユーザーが購入検討している商品と比較可能になります。 #### 1. 初期化 -Virtusizeにリクエストを送信する前に、**user ID**が設定されていることを確認してください。以下、どちらの方法でも **user ID** を設定することが可能です。 +Virtusizeにリクエストを送信する前に、**user ID**が設定されていることを確認してください。以下、どちらの方法でも +**user ID** を設定することが可能です。 -​ - アプリローンチ前に、アプリ内クラスのonCreateにて設定 +​ - アプリローンチ前に、アプリ内クラスのonCreateにて設定 -​ - アプリローンチ後に、アクティビティやフラグメントで設定 +​ - アプリローンチ後に、アクティビティやフラグメントで設定 * Kotlin @@ -694,10 +694,10 @@ lateinit var Virtusize: Virtusize override fun onCreate() { super.onCreate() Virtusize = VirtusizeBuilder().init(this) - .setApiKey(api_key) - .setUserId(user_id) - .setEnv(VirtusizeEnvironment.STAGING) - .build() + .setApiKey(api_key) + .setUserId(user_id) + .setEnv(VirtusizeEnvironment.STAGING) + .build() } // アプリローンチ後に、アクティビティやフラグメントで設定 @@ -709,23 +709,22 @@ override fun onCreate() { ~~~~java // アプリローンチ前に、アプリ内クラスのonCreateにて設定する場合 Virtusize Virtusize; + @Override public void onCreate() { -super.onCreate(); -Virtusize = new VirtusizeBuilder() - .init(this) - .setApiKey(api_key) - .setUserId(user_id) - .setEnv(VirtusizeEnvironment.STAGING) - .build(); - + super.onCreate(); + Virtusize = new VirtusizeBuilder() + .init(this) + .setApiKey(api_key) + .setUserId(user_id) + .setEnv(VirtusizeEnvironment.STAGING) + .build(); + // アプリローンチ後に、アクティビティやフラグメントで設定 -app = (App) getApplication(); -app.vituriszesetUserId("user_id"); + app = (App) getApplication(); + app.vituriszesetUserId("user_id"); ~~~~ - - #### 2. 注文データ向けに*VirtusizeOrder* オブジェクトを作成 *VirtusizeOrder*オブジェクトはViturisze#sendOrderに情報を送るもので、下記の項目が必要です。 @@ -734,26 +733,26 @@ app.vituriszesetUserId("user_id"); **VirtusizeOrder** -| **項目** | **データ形式** | **例** | **詳細** | -| ---------------- | --------------------------------------- | ------------- | ---------------------------------------- | -| externalOrderId* | String | "20200601586" | クライアント様でご使用している注文IDです | -| items* | VirtusizeOrderItem オブジェクトのリスト | 次項の表参照 | 注文商品の詳細リストです | +| **項目** | **データ形式** | **例** | **詳細** | +|------------------|-------------------------------|---------------|-----------------------| +| externalOrderId* | String | "20200601586" | クライアント様でご使用している注文IDです | +| items* | VirtusizeOrderItem オブジェクトのリスト | 次項の表参照 | 注文商品の詳細リストです | **VirtusizeOrderItem** -| **項目** | **データ形式** | **例** | **詳細** | -| ---------- | -------------- | ---------------------------------------- | ------------------------------------------------------------ | -| productId* | String | "A001" | 商品詳細ページで設定いただいているproductIDと同じもの | -| size* | String | "S", "M"など. | サイズ名称 | -| sizeAlias | String | "Small", "Large"など. | 前述のサイズ名称が商品詳細ページと異なる場合のAlias | -| variantId | String | "A001_SIZES_RED" | 商品の SKU、色、サイズなどの情報を設定してください。 | -| imageUrl* | String | "http[]()://images.example.com/coat.jpg" | 商品画像の URL です。この画像がバーチャサイズのサービスに登録されます。 | -| color | String | "RED", "R'など. | 商品の色を設定してください。 | -| gender | String | "W", "Women"など. | 性別を設定してください。 | -| unitPrice* | Double | 5100.00 | 最大12桁の商品単価を設定してください。 | -| currency* | String | "JPY", "KRW", "USD"など. | 通貨コードを設定してください。 | -| quantity* | Int | 1 | 購入数量を設定してください。 | -| url | String | "http[]()://example.com/products/A001" | 商品ページのURLを設定してください。一般ユーザーがアクセス可能なURLで設定が必要です。 | +| **項目** | **データ形式** | **例** | **詳細** | +|------------|-----------|------------------------------------------|-----------------------------------------------| +| productId* | String | "A001" | 商品詳細ページで設定いただいているproductIDと同じもの | +| size* | String | "S", "M"など. | サイズ名称 | +| sizeAlias | String | "Small", "Large"など. | 前述のサイズ名称が商品詳細ページと異なる場合のAlias | +| variantId | String | "A001_SIZES_RED" | 商品の SKU、色、サイズなどの情報を設定してください。 | +| imageUrl* | String | "http[]()://images.example.com/coat.jpg" | 商品画像の URL です。この画像がバーチャサイズのサービスに登録されます。 | +| color | String | "RED", "R'など. | 商品の色を設定してください。 | +| gender | String | "W", "Women"など. | 性別を設定してください。 | +| unitPrice* | Double | 5100.00 | 最大12桁の商品単価を設定してください。 | +| currency* | String | "JPY", "KRW", "USD"など. | 通貨コードを設定してください。 | +| quantity* | Int | 1 | 購入数量を設定してください。 | +| url | String | "http[]()://example.com/products/A001" | 商品ページのURLを設定してください。一般ユーザーがアクセス可能なURLで設定が必要です。 | **例** @@ -783,23 +782,23 @@ order.items = mutableListOf( ~~~~java VirtusizeOrder order = new VirtusizeOrder("20200601586"); ArrayList items = new ArrayList<>(); -items.add(new VirtusizeOrderItem( +items. + +add(new VirtusizeOrderItem( "A001", "L", - "Large", - "A001_SIZEL_RED", - "http://images.example.com/products/A001/red/image1xl.jpg", - "Red", - "W", - 5100.00, - "JPY", - 1, - "http://example.com/products/A001" + "Large", + "A001_SIZEL_RED", + "http://images.example.com/products/A001/red/image1xl.jpg", + "Red", + "W", + 5100.00, + "JPY", + 1, + "http://example.com/products/A001" )); ~~~~ - - #### 3. 注文情報の送信 ユーザーが注文完了時、ActivityあるいはFragment内で `Viturisze#sendOrder`を呼び出してください。 @@ -828,38 +827,34 @@ items.add(new VirtusizeOrderItem( ~~~~java app.vituriszesendOrder(order, - // This success callback is optional and gets called when the app successfully sends the order + // This success callback is optional and gets called when the app successfully sends the order new SuccessResponseHandler() { - @Override - public void onSuccess(@Nullable Object data) { - Log.i(TAG, "Successfully sent the order"); - } - }, + @Override + public void onSuccess (@Nullable Object data){ + Log.i(TAG, "Successfully sent the order"); + } +}, // This error callback is optional and gets called when an error occurs when the app is sending the order - new ErrorResponseHandler() { - @Override - public void onError(@NotNull VirtusizeError error) { - Log.e(TAG, error.getMessage()); - } - } + new + +ErrorResponseHandler() { + @Override + public void onError (@NotNull VirtusizeError error){ + Log.e(TAG, error.getMessage()); + } +} ); ~~~~ - - ## Enable SNS Login in Virtusize for native WebView apps Use the [Virtusize Auth SDK](https://github.com/virtusize/virtusize_auth_android) - - ## Examples 1. Kotlin example https://github.com/virtusize/integration_android/tree/master/sampleAppKotlin 2. Java example https://github.com/virtusize/integration_android/tree/master/sampleappjava - - ## License Copyright (c) 2018-24 Virtusize CO LTD (https://www.virtusize.jp) diff --git a/README.md b/README.md index f16597f0..f114b161 100644 --- a/README.md +++ b/README.md @@ -4,18 +4,23 @@ [日本語](https://github.com/virtusize/integration_android/blob/master/README-JP.md) -Virtusize helps retailers to illustrate the size and fit of clothing, shoes and bags online, by letting customers compare the -measurements of an item they want to buy (on a retailer's product page) with an item that they already own (a reference item). -This is done by comparing the silhouettes of the retailer's product with the silhouette of the customer's reference Item. -Virtusize is a widget which opens when clicking on the Virtusize button, which is located next to the size selection on the product page. +Virtusize helps retailers to illustrate the size and fit of clothing, shoes and bags online, by +letting customers compare the +measurements of an item they want to buy (on a retailer's product page) with an item that they +already own (a reference item). +This is done by comparing the silhouettes of the retailer's product with the silhouette of the +customer's reference Item. +Virtusize is a widget which opens when clicking on the Virtusize button, which is located next to +the size selection on the product page. Read more about Virtusize at https://www.virtusize.com -You need a unique API key and an Admin account, only available to Virtusize customers. [Contact our sales team](mailto:sales@virtusize.com) to become a customer. - -> This is the integration script for native Android apps only. For web integration, refer to the developer documentation on https://developers.virtusize.com. For iOS integration, refer to https://github.com/virtusize/integration_ios - +You need a unique API key and an Admin account, only available to Virtusize +customers. [Contact our sales team](mailto:sales@virtusize.com) to become a customer. +> This is the integration script for native Android apps only. For web integration, refer to the +> developer documentation on https://developers.virtusize.com. For iOS integration, refer +> to https://github.com/virtusize/integration_ios ## Table of Contents @@ -23,29 +28,30 @@ You need a unique API key and an Admin account, only available to Virtusize cust - [Getting Started](#getting-started) - - [Installation](#1-installation) - - [Proguard Rules](#2-proguard-rules) + - [Installation](#1-installation) + - [Proguard Rules](#2-proguard-rules) - [Setup](#setup) - - [Initialize Virtusize](#1-initialize-virtusize) - - [Load Product with Virtusize SDK](#2-load-product-with-virtusize-sdk) - - [Enable SNS Authentication](#3-enable-sns-authentication) - - [Register Virtusize Message Handler (Optional)](#4-register-virtusize-message-handler-optional) - - [Unregister Virtusize Message Handler (Optional)](#5-unregister-virtusize-message-handler-optional) + - [Initialize Virtusize](#1-initialize-virtusize) + - [Load Product with Virtusize SDK](#2-load-product-with-virtusize-sdk) + - [Enable SNS Authentication](#3-enable-sns-authentication) + - [Register Virtusize Message Handler (Optional)](#4-register-virtusize-message-handler-optional) + - [Unregister Virtusize Message Handler (Optional)](#5-unregister-virtusize-message-handler-optional) - [Virtusize Views](#virtusize-views) - - [Virtusize Button](#1-virtusize-button) - - [Virtusize InPage](#2-virtusize-inpage) - - [InPage Standard](#2-inpage-standard) - - [InPage Mini](#3-inpage-mini) + - [Virtusize Button](#1-virtusize-button) + - [Virtusize InPage](#2-virtusize-inpage) + - [InPage Standard](#2-inpage-standard) + - [InPage Mini](#3-inpage-mini) - [The Order API](#the-order-api) - - [Initialization](#1-initialization) - - [Create a *VirtusizeOrder* object for order data](#2-create-a-virtusizeorder-object-for-order-data) - - [Send an Order](#3-send-an-order) + - [Initialization](#1-initialization) + - [Create a + *VirtusizeOrder* object for order data](#2-create-a-virtusizeorder-object-for-order-data) + - [Send an Order](#3-send-an-order) - [Enable SNS Login in Virtusize for native Webview apps](#enable-sns-login-in-virtusize-for-native-webview-apps) @@ -53,27 +59,22 @@ You need a unique API key and an Admin account, only available to Virtusize cust - [License](#license) - - ## Requirements - minSdkVersion >= 21 - compileSdkVersion >= 34 - Setup in AppCompatActivity - - ## Getting Started -If you'd like to continue using the old Version 1.x.x, refer to the branch [v1](https://github.com/virtusize/integration_android/tree/v1). - - +If you'd like to continue using the old Version 1.x.x, refer to the +branch [v1](https://github.com/virtusize/integration_android/tree/v1). ### 1. Installation In your app `build.gradle` file, add the following dependencies: -- Groovy +- Groovy (build.gradle) ```groovy dependencies { @@ -81,7 +82,7 @@ In your app `build.gradle` file, add the following dependencies: } ``` -- Kotlin +- Kotlin (build.gradle.kts) ```kotlin dependencies { @@ -89,8 +90,6 @@ In your app `build.gradle` file, add the following dependencies: } ``` - - ### 2. Proguard Rules If you are using Proguard, add following rules to your proguard rules file: @@ -99,25 +98,25 @@ If you are using Proguard, add following rules to your proguard rules file: -keep class com.virtusize.android.** ``` - - ## Setup ### 1. Initialize Virtusize -Initialize the Virtusize object in your Application class's `onCreate` method using the **VirtusizeBuilder** to set up the configuration. Possible configuration methods are shown in the following table: +Initialize the Virtusize object in your Application class's `onCreate` method using the * +*VirtusizeBuilder** to set up the configuration. Possible configuration methods are shown in the +following table: **VirtusizeBuilder** -| Method | Argument Type | Example | Description | Requirement | -| -------------------- | --------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | -| setApiKey | String | setApiKey("api_key") | A unique API key is provided to each Virtusize client. | Yes | -| setUserId | String | setUserId("123") | Passed from the client if the user is logged into the client's app. | Yes, if the Order API is used. | -| setEnv | VirtusizeEnvironment | setEnv(VirtusizeEnvironment.STAGING) | The environment is the region you are running the integration from, either `VirtusizeEnvironment.STAGING`, `VirtusizeEnvironment.GLOBAL`, `VirtusizeEnvironment.JAPAN` or `VirtusizeEnvironment.KOREA`. | No. By default, the Virtusize environment will be set to `VirtusizeEnvironment.GLOBAL`. | -| setLanguage | VirtusizeLanguage | setLanguage(VirtusizeLanguage.EN) | Sets the initial language that the integration will load in. The possible values are `VirtusizeLanguage.EN`, `VirtusizeLanguage.JP` and `VirtusizeLanguage.KR` | No. By default, the initial language will be set based on the Virtusize environment. | -| setShowSGI | Boolean | setShowSGI(true) | Determines whether the integration will fetch SGI and use SGI flow for users to add user generated items to their wardrobe. | No. By default, ShowSGI is set to false | -| setAllowedLanguages | A list of `VirtusizeLanguage` | In Kotlin, setAllowedLanguages(mutableListOf(VirtusizeLanguage.EN, VirtusizeLanguage.JP))
In Java, setAllowedLanguages(Arrays.asList(VirtusizeLanguage.EN, VirtusizeLanguage.JP)) | The languages which the user can switch to using the Language Selector | No. By default, the integration allows all possible languages to be displayed, including English, Japanese and Korean. | -| setDetailsPanelCards | A list of `VirtusizeInfoCategory` | In Kotlin, setDetailsPanelCards(mutableListOf(VirtusizeInfoCategory.BRAND_SIZING, VirtusizeInfoCategory.GENERAL_FIT))
In Java, setDetailsPanelCards(Arrays.asList(VirtusizeInfoCategory.BRAND_SIZING, VirtusizeInfoCategory.GENERAL_FIT)) | The info categories which will be display in the Product Details tab. Possible categories are: `VirtusizeInfoCategory.MODEL_INFO`, `VirtusizeInfoCategory.GENERAL_FIT`, `VirtusizeInfoCategory.BRAND_SIZING` and `VirtusizeInfoCategory.MATERIAL` | No. By default, the integration displays all the possible info categories in the Product Details tab. | +| Method | Argument Type | Example | Description | Requirement | +|----------------------|-----------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------| +| setApiKey | String | setApiKey("api_key") | A unique API key is provided to each Virtusize client. | Yes | +| setUserId | String | setUserId("123") | Passed from the client if the user is logged into the client's app. | Yes, if the Order API is used. | +| setEnv | VirtusizeEnvironment | setEnv(VirtusizeEnvironment.STAGING) | The environment is the region you are running the integration from, either `VirtusizeEnvironment.STAGING`, `VirtusizeEnvironment.GLOBAL`, `VirtusizeEnvironment.JAPAN` or `VirtusizeEnvironment.KOREA`. | No. By default, the Virtusize environment will be set to `VirtusizeEnvironment.GLOBAL`. | +| setLanguage | VirtusizeLanguage | setLanguage(VirtusizeLanguage.EN) | Sets the initial language that the integration will load in. The possible values are `VirtusizeLanguage.EN`, `VirtusizeLanguage.JP` and `VirtusizeLanguage.KR` | No. By default, the initial language will be set based on the Virtusize environment. | +| setShowSGI | Boolean | setShowSGI(true) | Determines whether the integration will fetch SGI and use SGI flow for users to add user generated items to their wardrobe. | No. By default, ShowSGI is set to false | +| setAllowedLanguages | A list of `VirtusizeLanguage` | In Kotlin, setAllowedLanguages(mutableListOf(VirtusizeLanguage.EN, VirtusizeLanguage.JP))
In Java, setAllowedLanguages(Arrays.asList(VirtusizeLanguage.EN, VirtusizeLanguage.JP)) | The languages which the user can switch to using the Language Selector | No. By default, the integration allows all possible languages to be displayed, including English, Japanese and Korean. | +| setDetailsPanelCards | A list of `VirtusizeInfoCategory` | In Kotlin, setDetailsPanelCards(mutableListOf(VirtusizeInfoCategory.BRAND_SIZING, VirtusizeInfoCategory.GENERAL_FIT))
In Java, setDetailsPanelCards(Arrays.asList(VirtusizeInfoCategory.BRAND_SIZING, VirtusizeInfoCategory.GENERAL_FIT)) | The info categories which will be display in the Product Details tab. Possible categories are: `VirtusizeInfoCategory.MODEL_INFO`, `VirtusizeInfoCategory.GENERAL_FIT`, `VirtusizeInfoCategory.BRAND_SIZING` and `VirtusizeInfoCategory.MATERIAL` | No. By default, the integration displays all the possible info categories in the Product Details tab. | - Kotlin @@ -175,66 +174,65 @@ Initialize the Virtusize object in your Application class's `onCreate` method us } ``` - - ### 2. Load Product with Virtusize SDK -1. Inside your activity, +1. Inside your activity, + + - Create a `VirtusizeProduct` object with: - - Create a `VirtusizeProduct` object with: - - An `exernalId` that will be used to reference the product in the Virtusize server - - An `imageURL` for the product image - - Pass the `VirtusizeProduct` object to the `Virtusize#load` function +- An `exernalId` that will be used to reference the product in the Virtusize server + - An `imageURL` for the product image +- Pass the `VirtusizeProduct` object to the `Virtusize#load` function - Kotlin +Kotlin ```kotlin val product = VirtusizeProduct( - // Set the product's external ID - externalId = "vs_dress", - // Set the product image URL + // Set the product's external ID + externalId = "vs_dress", + // Set the product image URL imageUrl = "http://www.image.com/goods/12345.jpg" - ) +) - (application as App).virtusize.load(product) +(application as App).virtusize.load(product) ``` - Java +Java ```java VirtusizeProduct product = new VirtusizeProduct( - "vs_dress", - "http://www.image.com/goods/12345.jpg" - ); + "vs_dress", + "http://www.image.com/goods/12345.jpg" +); - app.virtusize.load(product); - ``` - + app.virtusize. +load(product); + ``` ### 3. Enable SNS authentication -The SNS authentication flow requires opening a Chrome Custom Tab, which will load a web page for the user to login with their SNS account. A custom URL scheme must be defined to return the login response to your app from a Chrome Custom Tab. +The SNS authentication flow requires opening a Chrome Custom Tab, which will load a web page for the +user to login with their SNS account. A custom URL scheme must be defined to return the login +response to your app from a Chrome Custom Tab. -Edit your `AndroidManifest.xml` file to include an intent filter and a `` tag for the custom URL scheme. +Edit your `AndroidManifest.xml` file to include an intent filter and a `` tag for the custom +URL scheme. ```xml + - - + package="com.your-company.your-app"> + + - + - - + + @@ -244,13 +242,13 @@ Edit your `AndroidManifest.xml` file to include an intent filter and a `` **❗IMPORTANT** 1. The URL host has to be `sns-auth` -2. The URL scheme must begin with your app's package ID (com.your-company.your-app) and **end with .virtusize**, and the scheme which you define must use all **lowercase** letters. - - +2. The URL scheme must begin with your app's package ID (com.your-company.your-app) and **end with + .virtusize**, and the scheme which you define must use all **lowercase** letters. ### 4. Register Virtusize Message Handler (Optional) -Please do not forget to unregister message handler in activity or fragment's lifecycle method before it dies or is removed. See the next section for a how-to. +Please do not forget to unregister message handler in activity or fragment's lifecycle method before +it dies or is removed. See the next section for a how-to. - Kotlin @@ -298,11 +296,14 @@ Please do not forget to unregister message handler in activity or fragment's lif } ``` - - ### 5. Unregister Virtusize Message Handler (Optional) -A message handler is tied to an activity or fragment's lifecycle, but the Virtusize library object is tied to the application's lifecycle. So if you forget to unregister message handler, then it will keep listening to events even after activity is dead or fragment has been removed. In the case of an activity; depending on where in the lifecycle you registered the message handler, you may need to unregister it in your `onPause` or `onStop` method before the super method is called. Follow the same guidelines in the case of fragment as well. +A message handler is tied to an activity or fragment's lifecycle, but the Virtusize library object +is tied to the application's lifecycle. So if you forget to unregister message handler, then it will +keep listening to events even after activity is dead or fragment has been removed. In the case of an +activity; depending on where in the lifecycle you registered the message handler, you may need to +unregister it in your `onPause` or `onStop` method before the super method is called. Follow the +same guidelines in the case of fragment as well. - Kotlin @@ -326,8 +327,6 @@ A message handler is tied to an activity or fragment's lifecycle, but the Virtus } ``` - - ## Virtusize Views After setting up the SDK, add a `VirtusizeView` to allow your customers to find their ideal size. @@ -338,27 +337,25 @@ Virtusize SDK provides two main UI components for clients to use: #### (1) Introduction -VirtusizeButton is the simplest UI Button for our SDK. It opens our application in the web view to support customers finding the right size. - - +VirtusizeButton is the simplest UI Button for our SDK. It opens our application in the web view to +support customers finding the right size. #### (2) Default Styles There are two default styles of the Virtusize Button in our Virtusize SDK. -| Teal Theme | Black Theme | -| :----------------------------------------------------------: | :----------------------------------------------------------: | +| Teal Theme | Black Theme | +|:-----------------------------------------------------------------------------------------------------------------:|:-----------------------------------------------------------------------------------------------------------------:| | | | - If you like, you can also customize the button style. - - +If you like, you can also customize the button style. #### (3) Usage **A. Add a VirtusizeButton in your activity's XML layout file.** -In order to use our default button styles, set `app:virtusizeButtonStyle="virtusize_black"` or `app:virtusizeButtonStyle="virtusize_teal"` in XML: +In order to use our default button styles, set `app:virtusizeButtonStyle="virtusize_black"` +or `app:virtusizeButtonStyle="virtusize_teal"` in XML: - XML @@ -370,7 +367,7 @@ In order to use our default button styles, set `app:virtusizeButtonStyle="virtus android:layout_height="wrap_content" /> ``` - or programmatically: +or programmatically: - Kotlin @@ -384,18 +381,19 @@ In order to use our default button styles, set `app:virtusizeButtonStyle="virtus virtusizeButton.setVirtusizeViewStyle(VirtusizeViewStyle.TEAL); ``` -**B. You can also use any other button styles and/or define the button's attributes like text, height, width, etc.** +**B. You can also use any other button styles and/or define the button's attributes like text, +height, width, etc.** ```xml - + + ``` -**C. Connect the Virtusize button, along with the** `VirtusizeProduct` **object (which you have passed to ** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** +**C. Connect the Virtusize button, along with the** `VirtusizeProduct` **object (which you have +passed to ** `Virtusize#load`) **into the Virtusize API by using the +** `Virtusize#setupVirtusizeView` **function in your activity.** - Kotlin @@ -409,108 +407,110 @@ In order to use our default button styles, set `app:virtusizeButtonStyle="virtus app.virtusize.setupVirtusizeView(virtusizeButton, product); ``` - - ### 2. Virtusize InPage #### (1) Introduction -Virtusize InPage is a button that behaves like a start button for our service. The button also behaves as a fitting guide that supports people to find the right size. +Virtusize InPage is a button that behaves like a start button for our service. The button also +behaves as a fitting guide that supports people to find the right size. ##### InPage types There are two types of InPage in our Virtusize SDK. -| InPage Standard | InPage Mini | -| :----------------------------------------------------------: | :----------------------------------------------------------: | +| InPage Standard | InPage Mini | +|:----------------------------------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------------------------------:| | ![InPageStandard](https://user-images.githubusercontent.com/7802052/92671977-9cb1fe80-f352-11ea-803b-5e3cb3469be4.png) | ![InPageMini](https://user-images.githubusercontent.com/7802052/92671979-9e7bc200-f352-11ea-8594-ed441649855c.png) | ⚠️**Caution**⚠️ -1. InPage cannot be implemented together with the Virtusize button. Please pick either InPage or Virtusize button for your online shop. +1. InPage cannot be implemented together with the Virtusize button. Please pick either InPage or + Virtusize button for your online shop. 2. InPage Mini must always be used in combination with InPage Standard. - - #### (2) InPage Standard ##### A. Usage - **Add a VirtusizeInPageStand in your activity's XML layout file.** - 1. In order to use our default styles, set `app:virtusizeInPageStandardStyle="virtusize_black"` or `app:virtusizeInPageStandardStyle="virtusize_teal"` - - 2. If you'd like to change the background color of the CTA button, you can use `app:inPageStandardButtonBackgroundColor="#123456"` - - 3. If you'd like to set the horizontal margins between the edges of the app screen and the InPageStandard, you can use `app:inPageStandardHorizontalMargin="16dp"` - - 4. If you'd like to change the font sizes of the InPageStandard, you can use `app:inPageStandardMessageTextSize="10sp"` and `app:inPageStandardButtonTextSize="10sp"`. - - - XML - - ```xml - - ``` - - ```xml - - ``` - - or programmatically: - - - Kotlin - - ```kotlin - // Set the Virtusize view style - exampleVirtusizeInPageStandard.virtusizeViewStyle = VirtusizeViewStyle.TEAL - // Set the horizontal margins between the edges of the app screen and the InPageStandard - // Note: Use the helper extension function `dpInPx` if you like - exampleVirtusizeInPageStandard.horizontalMargin = 16.dpInPx - // Set the background color of the check size button in InPage Standard - exampleVirtusizeInPageStandard.setButtonBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)) - // Set the text sizes of the InPage message and the check size button - exampleVirtusizeInPageStandard.messageTextSize = 10f.spToPx - exampleVirtusizeInPageStandard.buttonTextSize = 10f.spToPx - ``` - - - Java - - ```java - virtusizeInPageStandard.setVirtusizeViewStyle(VirtusizeViewStyle.BLACK); - virtusizeInPageStandard.setHorizontalMargin(ExtensionsKt.getDpInPx(16)); - virtusizeInPageStandard.setButtonBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)); - virtusizeInPageStandard.setMessageTextSize(ExtensionsKt.getSpToPx(10)); - virtusizeInPageStandard.setButtonTextSize(ExtensionsKt.getSpToPx(10)); - ``` - -- **Connect the InPage Standard, along with the** `VirtusizeProduct` **object (which you have passed to ** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** - - - Kotlin - - ```kotlin - (application as App) - .virtusize - .setupVirtusizeView(exampleVirtusizeInPageStandard, product) - ``` - - - Java - - ```java - app.virtusize.setupVirtusizeView(virtusizeInPageStandard, product); - ``` - - + 1. In order to use our default styles, set `app:virtusizeInPageStandardStyle="virtusize_black"` + or `app:virtusizeInPageStandardStyle="virtusize_teal"` + + 2. If you'd like to change the background color of the CTA button, you can + use `app:inPageStandardButtonBackgroundColor="#123456"` + + 3. If you'd like to set the horizontal margins between the edges of the app screen and the + InPageStandard, you can use `app:inPageStandardHorizontalMargin="16dp"` + + 4. If you'd like to change the font sizes of the InPageStandard, you can + use `app:inPageStandardMessageTextSize="10sp"` and `app:inPageStandardButtonTextSize="10sp"`. + + - XML + + ```xml + + ``` + + ```xml + + ``` + + or programmatically: + + - Kotlin + + ```kotlin + // Set the Virtusize view style + exampleVirtusizeInPageStandard.virtusizeViewStyle = VirtusizeViewStyle.TEAL + // Set the horizontal margins between the edges of the app screen and the InPageStandard + // Note: Use the helper extension function `dpInPx` if you like + exampleVirtusizeInPageStandard.horizontalMargin = 16.dpInPx + // Set the background color of the check size button in InPage Standard + exampleVirtusizeInPageStandard.setButtonBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)) + // Set the text sizes of the InPage message and the check size button + exampleVirtusizeInPageStandard.messageTextSize = 10f.spToPx + exampleVirtusizeInPageStandard.buttonTextSize = 10f.spToPx + ``` + + - Java + + ```java + virtusizeInPageStandard.setVirtusizeViewStyle(VirtusizeViewStyle.BLACK); + virtusizeInPageStandard.setHorizontalMargin(ExtensionsKt.getDpInPx(16)); + virtusizeInPageStandard.setButtonBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)); + virtusizeInPageStandard.setMessageTextSize(ExtensionsKt.getSpToPx(10)); + virtusizeInPageStandard.setButtonTextSize(ExtensionsKt.getSpToPx(10)); + ``` + +- **Connect the InPage Standard, along with the** `VirtusizeProduct` **object (which you have passed + to ** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` * + *function in your activity.** + + - Kotlin + + ```kotlin + (application as App) + .virtusize + .setupVirtusizeView(exampleVirtusizeInPageStandard, product) + ``` + + - Java + + ```java + app.virtusize.setupVirtusizeView(virtusizeInPageStandard, product); + ``` ##### B. Design Guidelines @@ -519,7 +519,7 @@ There are two types of InPage in our Virtusize SDK. There are two default design variations. | Teal Theme | Black Theme | - | :----------------------------------------------------------: | :----------------------------------------------------------: | + | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![InPageStandardTeal](https://user-images.githubusercontent.com/7802052/92672035-b9e6cd00-f352-11ea-9e9e-5385a19e96da.png) | ![InPageStandardBlack](https://user-images.githubusercontent.com/7802052/92672031-b81d0980-f352-11ea-8b7a-564dd6c2a7f1.png) | - ##### Layout Variations @@ -527,109 +527,112 @@ There are two types of InPage in our Virtusize SDK. Here are some possible layouts | 1 thumbnail + 2 lines of message | 2 thumbnails + 2 lines of message | - | :----------------------------------------------------------: | :----------------------------------------------------------: | + | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![1 thumbnail + 2 lines of message](https://user-images.githubusercontent.com/7802052/97399368-5e879300-1930-11eb-8b77-b49e06813550.png) | ![2 thumbnails + 2 lines of message](https://user-images.githubusercontent.com/7802052/97399370-5f202980-1930-11eb-9a2d-7b71714aa7b4.png) | | **1 thumbnail + 1 line of message** | **2 animated thumbnails + 2 lines of message** | | ![1 thumbnail + 1 line of message](https://user-images.githubusercontent.com/7802052/97399373-5f202980-1930-11eb-81fe-9946b656eb4c.png) | ![2 animated thumbnails + 2 lines of message](https://user-images.githubusercontent.com/7802052/97399355-59c2df00-1930-11eb-8a52-292956b8762d.gif) | - ##### Recommended Placement - - Near the size table + - Near the size table - - In the size info section + - In the size info section - ##### UI customization - - **You can:** - - change the background color of the CTA button as long as it passes **[WebAIM contrast test](https://webaim.org/resources/contrastchecker/)**. - - change the width of InPage so it fits your application width. - - - **You cannot:** - - change interface components such as shapes and spacing. - - change the font. - - change the CTA button shape. - - change messages. - - change or hide the box shadow. - - hide the footer that contains VIRTUSIZE logo and Privacy Policy text link. - + - **You can:** + - change the background color of the CTA button as long as it passes * + *[WebAIM contrast test](https://webaim.org/resources/contrastchecker/)**. + - change the width of InPage so it fits your application width. + - **You cannot:** + - change interface components such as shapes and spacing. + - change the font. + - change the CTA button shape. + - change messages. + - change or hide the box shadow. + - hide the footer that contains VIRTUSIZE logo and Privacy Policy text link. #### (3) InPage Mini -This is a mini version of InPage you can place in your application. The discreet design is suitable for layouts where customers are browsing product images and size tables. +This is a mini version of InPage you can place in your application. The discreet design is suitable +for layouts where customers are browsing product images and size tables. ##### A. Usage - **Add a VirtusizeInPageMini in your activity's XML layout file.** - 1. In order to use our default styles, set `app:virtusizeInPageMiniStyle="virtusize_black"` or `app:virtusizeInPageMiniStyle="virtusize_teal"` + 1. In order to use our default styles, set `app:virtusizeInPageMiniStyle="virtusize_black"` + or `app:virtusizeInPageMiniStyle="virtusize_teal"` - 2. If you'd like to change the background color of the bar, you can use `app:inPageMiniBackgroundColor="#123456"` + 2. If you'd like to change the background color of the bar, you can + use `app:inPageMiniBackgroundColor="#123456"` - 3. If you'd like to change the font sizes, you can use `app:inPageMiniMessageTextSize="12sp"` and `app:inPageMiniButtonTextSize="10sp"`. + 3. If you'd like to change the font sizes, you can use `app:inPageMiniMessageTextSize="12sp"` + and `app:inPageMiniButtonTextSize="10sp"`. - - XML + - XML - ```xml - - ``` + ```xml + + ``` - ```xml - - ``` + ```xml + + ``` - or programmatically: + or programmatically: - - Kotlin + - Kotlin - ```kotlin - // Set the Virtusize view style - exampleVirtusizeInPageMini.virtusizeViewStyle = VirtusizeViewStyle.BLACK - // Set the background color of the InPageMini view - exampleVirtusizeInPageMini.setInPageMiniBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)) - // Set the text sizes of the InPage message and the check size button - exampleVirtusizeInPageMini.messageTextSize = 12f.spToPx - exampleVirtusizeInPageMini.buttonTextSize = 10f.spToPx - ``` + ```kotlin + // Set the Virtusize view style + exampleVirtusizeInPageMini.virtusizeViewStyle = VirtusizeViewStyle.BLACK + // Set the background color of the InPageMini view + exampleVirtusizeInPageMini.setInPageMiniBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)) + // Set the text sizes of the InPage message and the check size button + exampleVirtusizeInPageMini.messageTextSize = 12f.spToPx + exampleVirtusizeInPageMini.buttonTextSize = 10f.spToPx + ``` - - Java + - Java - ```java - virtusizeInPageMini.setVirtusizeViewStyle(VirtusizeViewStyle.TEAL); - virtusizeInPageMini.setInPageMiniBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)); - virtusizeInPageMini.setMessageTextSize(ExtensionsKt.getSpToPx(12)); - virtusizeInPageMini.setButtonTextSize(ExtensionsKt.getSpToPx(10)); - ``` - -- **Connect the InPage Mini, along with the** `VirtusizeProduct` **object (which you have passed to ** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` **function in your activity.** - - - Kotlin + ```java + virtusizeInPageMini.setVirtusizeViewStyle(VirtusizeViewStyle.TEAL); + virtusizeInPageMini.setInPageMiniBackgroundColor(ContextCompat.getColor(this, R.color.your_custom_color)); + virtusizeInPageMini.setMessageTextSize(ExtensionsKt.getSpToPx(12)); + virtusizeInPageMini.setButtonTextSize(ExtensionsKt.getSpToPx(10)); + ``` - ```kotlin - (application as App) - .virtusize - .setupVirtusizeView(exampleVirtusizeInPageMini, product) - ``` +- **Connect the InPage Mini, along with the** `VirtusizeProduct` **object (which you have passed + to ** `Virtusize#load`) **into the Virtusize API by using the** `Virtusize#setupVirtusizeView` * + *function in your activity.** - - Java + - Kotlin - ```java - app.virtusize.setupVirtusizeView(virtusizeInPageMini, product); - ``` + ```kotlin + (application as App) + .virtusize + .setupVirtusizeView(exampleVirtusizeInPageMini, product) + ``` + - Java + ```java + app.virtusize.setupVirtusizeView(virtusizeInPageMini, product); + ``` ##### B. Design Guidelines @@ -638,48 +641,49 @@ This is a mini version of InPage you can place in your application. The discreet There are two default design variations. | Teal Theme | Black Theme | - | :----------------------------------------------------------: | :----------------------------------------------------------: | + | :----------------------------------------------------------: | :----------------------------------------------------------: | | ![InPageMiniTeal](https://user-images.githubusercontent.com/7802052/92672234-2d88da00-f353-11ea-99d9-b9e9b6aa5620.png) | ![InPageMiniBlack](https://user-images.githubusercontent.com/7802052/92672232-2c57ad00-f353-11ea-80f6-55a9c72fb0b5.png) | - ##### Recommended Placements | Underneath the product image | Underneath or near the size table | - | :----------------------------------------------------------: | :----------------------------------------------------------: | + | :----------------------------------------------------------: | :----------------------------------------------------------: | | | | - ##### Default Fonts - - Japanese - - Noto Sans CJK JP - - 12sp (Message) - - 10sp (Button) - - Korean - - Noto Sans CJK KR - - 12sp (Message) - - 10sp (Button) - - English - - Roboto - - 14sp (Message) - - 12sp (Button) + - Japanese + - Noto Sans CJK JP + - 12sp (Message) + - 10sp (Button) + - Korean + - Noto Sans CJK KR + - 12sp (Message) + - 10sp (Button) + - English + - Roboto + - 14sp (Message) + - 12sp (Button) - ##### UI customization - - **You can:** - - change the background color of the bar as long as it passes **[WebAIM contrast test](https://webaim.org/resources/contrastchecker/)**. - - **You cannot:** - - change the font. - - change the CTA button shape. - - change messages. - - + - **You can:** + - change the background color of the bar as long as it passes * + *[WebAIM contrast test](https://webaim.org/resources/contrastchecker/)**. + - **You cannot:** + - change the font. + - change the CTA button shape. + - change messages. ## The Order API -The order API enables Virtusize to show your customers the items they have recently purchased as part of their `Purchase History`, and to use those items to compare with new items they want to buy. +The order API enables Virtusize to show your customers the items they have recently purchased as +part of their `Purchase History`, and to use those items to compare with new items they want to buy. #### 1. Initialization -Make sure to set up the **user ID** before sending orders to Virtusize. You can set up the user ID either: +Make sure to set up the **user ID** before sending orders to Virtusize. You can set up the user ID +either: in your Application class's `onCreate` method before the app is launched @@ -724,36 +728,35 @@ in your activity or fragment after the app is launched app.virtusize.setUserId("user_id"); ~~~~ - - #### 2. Create a *VirtusizeOrder* object for order data -The ***VirtusizeOrder*** object gets passed to the `Virtusize#sendOrder` method, and has the following attributes: +The ***VirtusizeOrder*** object gets passed to the `Virtusize#sendOrder` method, and has the +following attributes: __**Note:**__ * means the attribute is required **VirtusizeOrder** | Attribute | Data Type | Example | Description | -| ---------------- | -------------------------------------- | ------------------- | ----------------------------------- | +|------------------|----------------------------------------|---------------------|-------------------------------------| | externalOrderId* | String | "20200601586" | The order ID provided by the client | | items* | A list of `VirtusizeOrderItem` objects | See the table below | A list of the order items. | **VirtusizeOrderItem** -| Attribute | Data Type | Example | Description | -| ---------- | --------- | ---------------------------------------- | ------------------------------------------------------------ | -| productId* | String | "A001" | The external product ID provided by the client. It must be unique for each product. | -| size* | String | "S", "M", etc. | The name of the size | -| sizeAlias | String | "Small", "Large", etc. | The alias of the size is added if the size name is not identical from the product page | +| Attribute | Data Type | Example | Description | +|------------|-----------|------------------------------------------|------------------------------------------------------------------------------------------------| +| productId* | String | "A001" | The external product ID provided by the client. It must be unique for each product. | +| size* | String | "S", "M", etc. | The name of the size | +| sizeAlias | String | "Small", "Large", etc. | The alias of the size is added if the size name is not identical from the product page | | variantId | String | "A001_SIZES_RED" | An ID that is set on the product SKU, color, or size if there are several options for the item | -| imageUrl* | String | "http[]()://images.example.com/coat.jpg" | The image URL of the item | -| color | String | "RED", "R', etc. | The color of the item | -| gender | String | "W", "Women", etc. | An identifier for the gender | -| unitPrice* | Double | 5100.00 | The product price that is a double number with a maximum of 12 digits and 2 decimals (12, 2) | -| currency* | String | "JPY", "KRW", "USD", etc. | Currency code | -| quantity* | Int | 1 | The number of items purchased. If it's not passed, It will be set to 1 | -| url | String | "http[]()://example.com/products/A001" | The URL of the product page. Please make sure this is a URL that users can access | +| imageUrl* | String | "http[]()://images.example.com/coat.jpg" | The image URL of the item | +| color | String | "RED", "R', etc. | The color of the item | +| gender | String | "W", "Women", etc. | An identifier for the gender | +| unitPrice* | Double | 5100.00 | The product price that is a double number with a maximum of 12 digits and 2 decimals (12, 2) | +| currency* | String | "JPY", "KRW", "USD", etc. | Currency code | +| quantity* | Int | 1 | The number of items purchased. If it's not passed, It will be set to 1 | +| url | String | "http[]()://example.com/products/A001" | The URL of the product page. Please make sure this is a URL that users can access | **Samples** @@ -798,16 +801,14 @@ __**Note:**__ * means the attribute is required )); ~~~~ - - #### 3. Send an Order Call the `Virtusize#sendOrder` method in your activity or fragment when the user places an order. * Kotlin - The `onSuccess` and `onError` callbacks are optional. - + The `onSuccess` and `onError` callbacks are optional. + ~~~~kotlin (application as App) .virtusize @@ -825,8 +826,8 @@ Call the `Virtusize#sendOrder` method in your activity or fragment when the user * Java - The `SuccessResponseHandler` and `ErrorResponseHandler` callbacks are optional. - + The `SuccessResponseHandler` and `ErrorResponseHandler` callbacks are optional. + ~~~~java app.virtusize.sendOrder(order, // This success callback is optional and gets called when the app successfully sends the order @@ -846,21 +847,15 @@ Call the `Virtusize#sendOrder` method in your activity or fragment when the user ); ~~~~ - - ## Enable SNS Login in Virtusize for native WebView apps Use the [Virtusize Auth SDK](https://github.com/virtusize/virtusize_auth_android) - - ## Examples 1. Kotlin example https://github.com/virtusize/integration_android/tree/master/sampleAppKotlin 2. Java example https://github.com/virtusize/integration_android/tree/master/sampleAppJava - - ## License Copyright (c) 2018-24 Virtusize CO LTD (https://www.virtusize.jp) \ No newline at end of file From 558d5c6f009420029764dae6d394fa02e734efcf Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sun, 4 Aug 2024 11:13:48 +0900 Subject: [PATCH 17/19] Make mavenPublicationName private --- .../main/java/com/virtusize/android/extensions/Publications.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt b/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt index c76b0157..459dea4a 100644 --- a/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt +++ b/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt @@ -30,7 +30,7 @@ private fun Project.configureRepositories() { } } -val mavenPublicationName = "maven" +private val mavenPublicationName = "maven" private fun Project.configureMavenPublication( publishId: String, From d9b25d529b04512c27740534f3965b27fa80702e Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sun, 4 Aug 2024 11:41:35 +0900 Subject: [PATCH 18/19] Modify the publish script --- .github/workflows/publish.yml | 2 +- .../com/virtusize/android/extensions/Publications.kt | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bfd5173d..103b651e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -25,4 +25,4 @@ jobs: distribution: 'adopt' cache: gradle - name: Deploy to OSSRH and publish to MavenCentral - run: ./gradlew publishReleasePublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository \ No newline at end of file + run: ./gradlew publishMavenPublicationToSonatypeRepository --max-workers 1 closeAndReleaseSonatypeStagingRepository \ No newline at end of file diff --git a/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt b/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt index 459dea4a..b868c249 100644 --- a/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt +++ b/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt @@ -9,6 +9,9 @@ import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.get import org.gradle.plugins.signing.SigningExtension +private val isSnapshot: Boolean + get() = Constants.VERSION_NAME.endsWith("SNAPSHOT") + private fun Project.configureRepositories() { configure { publications { @@ -19,7 +22,7 @@ private fun Project.configureRepositories() { val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" url = - uri(if (Constants.VERSION_NAME.endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) + uri(if (isSnapshot) snapshotsRepoUrl else releasesRepoUrl) credentials { username = getProperties("OSSRH_USERNAME") password = getProperties("OSSRH_PASSWORD") @@ -83,11 +86,11 @@ private fun Project.configureMavenPublication( private fun Project.configureSigning(publication: PublishingExtension) { configure { - val shouldSign = + val hasSigningCredentials = getProperties("GPG_KEY_ID") != null && getProperties("GPG_KEY") != null && getProperties("GPG_KEY_PASSWORD") != null - isRequired = shouldSign && gradle.taskGraph.hasTask("publish") + isRequired = !isSnapshot && hasSigningCredentials if (isRequired) { useInMemoryPgpKeys( getProperties("GPG_KEY_ID"), From 8064d38817ba8f74799b86fd7d610f97acc24cdd Mon Sep 17 00:00:00 2001 From: Kuei-Jung Hu Date: Sun, 4 Aug 2024 11:51:23 +0900 Subject: [PATCH 19/19] Rename to areSigningCredentialsProvided --- .../java/com/virtusize/android/extensions/Publications.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt b/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt index b868c249..3ff88556 100644 --- a/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt +++ b/buildSrc/src/main/java/com/virtusize/android/extensions/Publications.kt @@ -86,11 +86,11 @@ private fun Project.configureMavenPublication( private fun Project.configureSigning(publication: PublishingExtension) { configure { - val hasSigningCredentials = + val areSigningCredentialsProvided = getProperties("GPG_KEY_ID") != null && getProperties("GPG_KEY") != null && getProperties("GPG_KEY_PASSWORD") != null - isRequired = !isSnapshot && hasSigningCredentials + isRequired = !isSnapshot && areSigningCredentialsProvided if (isRequired) { useInMemoryPgpKeys( getProperties("GPG_KEY_ID"),