From f60c8bb07e4d0c81b88e700025adb3a69e8ec638 Mon Sep 17 00:00:00 2001 From: Olga Salina Date: Wed, 20 Mar 2024 22:20:12 +0300 Subject: [PATCH 1/6] SDK-3651 Add warmup for webview and custom tabs --- xsolla-payments-sdk/build.gradle | 2 + .../src/main/AndroidManifest.xml | 9 ++ .../com/xsolla/android/payments/XPayments.kt | 27 +++- .../payments/caching/PayStationCache.kt | 137 ++++++++++++++++++ .../caching/PayStationCacheInitializer.kt | 14 ++ .../android/payments/ui/utils/BrowserUtils.kt | 10 +- .../payments/ui/utils/CustomTabsHelper.kt | 62 ++++++++ 7 files changed, 252 insertions(+), 9 deletions(-) create mode 100644 xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCache.kt create mode 100644 xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCacheInitializer.kt create mode 100644 xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/CustomTabsHelper.kt diff --git a/xsolla-payments-sdk/build.gradle b/xsolla-payments-sdk/build.gradle index dee6824e..737b86d0 100644 --- a/xsolla-payments-sdk/build.gradle +++ b/xsolla-payments-sdk/build.gradle @@ -41,6 +41,8 @@ dependencies { implementation "androidx.core:core-ktx:$androidx_core_ktx" implementation "androidx.browser:browser:$androidx_browser" + + implementation "androidx.startup:startup-runtime:1.0.0" } dokkaHtmlPartial { diff --git a/xsolla-payments-sdk/src/main/AndroidManifest.xml b/xsolla-payments-sdk/src/main/AndroidManifest.xml index cb8476cc..3c5b583a 100644 --- a/xsolla-payments-sdk/src/main/AndroidManifest.xml +++ b/xsolla-payments-sdk/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ @@ -30,6 +31,14 @@ android:scheme="app" /> + + + \ No newline at end of file diff --git a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/XPayments.kt b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/XPayments.kt index c68d14a3..d5c9f550 100644 --- a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/XPayments.kt +++ b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/XPayments.kt @@ -5,8 +5,8 @@ import android.content.Intent import android.net.Uri import android.os.Build import android.os.Parcelable -import android.util.Log import androidx.core.os.bundleOf +import com.xsolla.android.payments.caching.PayStationCache import com.xsolla.android.payments.data.AccessToken import com.xsolla.android.payments.ui.ActivityPayStation import com.xsolla.android.payments.util.AnalyticsUtils @@ -34,6 +34,7 @@ class XPayments { private var accessToken: AccessToken? = null private var isSandbox: Boolean = true private var useWebview: Boolean = false + private var payStationVersion: PayStationVersion = PayStationVersion.V4 private var redirectScheme: String = "app" // the same is set at AndroidManifest.xml private var redirectHost: String = @@ -68,13 +69,23 @@ class XPayments { apply { this.redirectHost = redirectHost.lowercase() } + /** + * Set pay station version + */ + fun setPayStationVersion(version: PayStationVersion) = + apply { this.payStationVersion = version } + + private fun getPayStationVersion() = when (payStationVersion) { + PayStationVersion.V3 -> "paystation3" + PayStationVersion.V4 -> "paystation4" + } + /** * Build the intent */ fun build(): Intent { val url = generateUrl() - val intent = Intent() - intent.setClass(context, ActivityPayStation::class.java) + var intent = PayStationCache.getInstance(context).getCachedIntent() intent.putExtras( bundleOf( ActivityPayStation.ARG_URL to url, @@ -91,7 +102,7 @@ class XPayments { val uriBuilder = Uri.Builder() .scheme("https") .authority(getServer()) - .appendPath("paystation3") + .appendPath(getPayStationVersion()) .appendQueryParameter("access_token", it.token) appendAnalytics(uriBuilder) @@ -148,4 +159,12 @@ class XPayments { UNKNOWN } + /** + * Pay Station version + */ + enum class PayStationVersion { + V3, + V4 + } + } \ No newline at end of file diff --git a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCache.kt b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCache.kt new file mode 100644 index 00000000..8735f14e --- /dev/null +++ b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCache.kt @@ -0,0 +1,137 @@ +package com.xsolla.android.payments.caching + +import android.content.Context +import android.content.Intent +import android.os.Build +import android.util.Log +import android.view.ViewGroup +import android.webkit.RenderProcessGoneDetail +import android.webkit.WebChromeClient +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.Toast +import androidx.annotation.RequiresApi +import androidx.browser.customtabs.CustomTabsSession +import com.xsolla.android.payments.XPayments +import com.xsolla.android.payments.ui.ActivityPayStation +import com.xsolla.android.payments.ui.utils.BrowserUtils +import com.xsolla.android.payments.ui.utils.CustomTabsHelper + + +class PayStationCache(val context: Context) { + + private lateinit var preloadingWebView:WebView + private var cachedIntent: Intent? = null + private lateinit var customTabHelper: CustomTabsHelper + + fun init() { + XPayments.createIntentBuilder(context) + + if(BrowserUtils.isCustomTabsBrowserAvailable(context)) { + customTabHelper = CustomTabsHelper(context) + customTabHelper.bindCustomTabsService() + } else { + preloadUrl("https://secure.xsolla.com/paystation3/ru/cache-warmup") + } + } + + fun getCachedIntent(): Intent { + if(cachedIntent == null) { + val newIntent = Intent() + newIntent.setClass(context, ActivityPayStation::class.java) + cachedIntent = newIntent + } + return cachedIntent!! + } + + fun getCachedSession(): CustomTabsSession? { + return customTabHelper.getSession() + } + + private fun preloadUrl(url: String) { + preloadingWebView = prepareWebView(context) + loadUrl(url, preloadingWebView) + } + + private fun prepareWebView(context: Context): WebView { + val webView = WebView(context) + setupWebViewWithDefaults(webView) + return webView + } + + private fun setupWebViewWithDefaults(webView: WebView) { + setWebViewSettings(webView) + setBrowserClients(webView) + } + + private fun setWebViewSettings(webView: WebView?) { + requireNotNull(webView) { "WebView should not be null!" } + webView.settings.javaScriptEnabled = true + webView.settings.domStorageEnabled = true + webView.settings.allowFileAccess = true + webView.settings.loadsImagesAutomatically = true + } + + private fun setBrowserClients(webView: WebView?) { + requireNotNull(webView) { "WebView should not be null!" } + try { + webView.webViewClient = object : WebViewClient() { + override fun shouldOverrideUrlLoading(webview: WebView, url: String): Boolean { + Log.d(TAG, "shouldOverrideUrlLoading intercept url: $url") + webView.loadUrl(url) + return true + } + + override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) { + val url = view.originalUrl + Toast.makeText(view.context, "Load failed with error: $description", Toast.LENGTH_LONG).show() + } + + @RequiresApi(Build.VERSION_CODES.O) + override fun onRenderProcessGone(view: WebView, detail: RenderProcessGoneDetail): Boolean { + if (!detail.didCrash()) { + Log.d(TAG, "System killed the WebView rendering process to reclaim memory. Recreating...") + if (view != null) { + val webViewContainer = view.parent as ViewGroup + if (webViewContainer != null && webViewContainer.childCount > 0) { + webViewContainer.removeView(view) + } + view.destroy() + } + return true + } + return false + } + } + webView.webChromeClient = object : WebChromeClient() { + override fun onProgressChanged(view: WebView, newProgress: Int) { + super.onProgressChanged(view, newProgress) + Log.d(TAG, "onProgressChanged: $newProgress") + if (view != null && newProgress == 100) { + val url = view.originalUrl + Log.d(TAG, "Preloading is done!") + } + } + } + } catch (e: Exception) { + Log.d(TAG, e.message, e) + } + } + + private fun loadUrl(url: String, webView: WebView) { + webView.loadUrl(url) + } + + companion object { + private var instance: PayStationCache? = null + + fun getInstance(context: Context): PayStationCache { + if(instance == null) { + instance = PayStationCache(context) + } + return instance!! + } + + private const val TAG = "PayStationCache" + } +} \ No newline at end of file diff --git a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCacheInitializer.kt b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCacheInitializer.kt new file mode 100644 index 00000000..54e94407 --- /dev/null +++ b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCacheInitializer.kt @@ -0,0 +1,14 @@ +package com.xsolla.android.payments.caching + +import android.content.Context +import androidx.startup.Initializer + +class PayStationCacheInitializer: Initializer { + override fun create(context: Context): PayStationCache { + return PayStationCache.getInstance(context).apply { init() } + } + + override fun dependencies(): List>> { + return emptyList() + } +} \ No newline at end of file diff --git a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/BrowserUtils.kt b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/BrowserUtils.kt index 469114bf..7c4ab177 100644 --- a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/BrowserUtils.kt +++ b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/BrowserUtils.kt @@ -9,6 +9,7 @@ import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION import androidx.core.content.ContextCompat import com.xsolla.android.payments.R +import com.xsolla.android.payments.caching.PayStationCache object BrowserUtils { @@ -26,7 +27,7 @@ object BrowserUtils { } } - private fun getAvailableCustomTabsBrowsers(context: Context): List { + fun getAvailableCustomTabsBrowsers(context: Context): List { val browserIntent = Intent() .setAction(Intent.ACTION_VIEW) .addCategory(Intent.CATEGORY_BROWSABLE) @@ -66,14 +67,13 @@ object BrowserUtils { ) .build() - val intent = CustomTabsIntent.Builder() + val customTabsIntent = CustomTabsIntent.Builder(PayStationCache.getInstance(context).getCachedSession()) .setDefaultColorSchemeParams(colorSchemeParams) .setShowTitle(true) .setUrlBarHidingEnabled(true) .build() - intent.intent.`package` = getAvailableCustomTabsBrowsers(context).first() - - intent.launchUrl(context, Uri.parse(url)) + customTabsIntent.intent.setPackage(getAvailableCustomTabsBrowsers(context).first()) + customTabsIntent.launchUrl(context, Uri.parse(url)) } fun launchPlainBrowser(activity: Activity, url: String) { diff --git a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/CustomTabsHelper.kt b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/CustomTabsHelper.kt new file mode 100644 index 00000000..b59b1069 --- /dev/null +++ b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/CustomTabsHelper.kt @@ -0,0 +1,62 @@ +package com.xsolla.android.payments.ui.utils + +import android.content.ComponentName +import android.content.Context +import android.net.Uri +import androidx.browser.customtabs.CustomTabsClient +import androidx.browser.customtabs.CustomTabsServiceConnection +import androidx.browser.customtabs.CustomTabsSession + +class CustomTabsHelper(private val context: Context) { + + private var customTabsSession: CustomTabsSession? = null + private var mClient: CustomTabsClient? = null + + private var connection: CustomTabsServiceConnection? = object : CustomTabsServiceConnection() { + override fun onCustomTabsServiceConnected(name: ComponentName, client: CustomTabsClient) { + if (client != null) { + mClient = client + client.warmup(0) + customTabsSession = client.newSession(null) + customTabsSession!!.mayLaunchUrl(Uri.parse("https://secure.xsolla.com/paystation3/ru/cache-warmup"), null, null) + customTabsSession!!.mayLaunchUrl(Uri.parse("https://secure.xsolla.com/paystation4/ru/cache-warmup"), null, null) + + } + } + + override fun onServiceDisconnected(name: ComponentName) { + mClient = null + customTabsSession = null + } + } + + fun bindCustomTabsService() { + + if (mClient != null) return + val availableBrowsers = BrowserUtils.getAvailableCustomTabsBrowsers(context) + + if(availableBrowsers.isEmpty()) return + + val packageName = BrowserUtils.getAvailableCustomTabsBrowsers(context).first() + + CustomTabsClient.bindCustomTabsService(context, packageName, connection!!) + } + + fun unbindCustomTabsService() { + connection ?: return + context.unbindService(connection!!) + mClient = null + customTabsSession = null + connection = null + } + + fun getSession(): CustomTabsSession? { + if (mClient == null) { + customTabsSession = null + } else if (customTabsSession == null) { + customTabsSession = mClient!!.newSession(null) + } + return customTabsSession + } + +} \ No newline at end of file From acd968b06d4a1407ba4c4e12f43272de38605cf6 Mon Sep 17 00:00:00 2001 From: Olga Salina Date: Mon, 25 Mar 2024 12:38:54 +0300 Subject: [PATCH 2/6] SDK-3648 Fix revision comments --- xsolla-payments-sdk/build.gradle | 2 +- .../com/xsolla/android/payments/XPayments.kt | 18 ++++++++++++------ .../payments/caching/PayStationCache.kt | 10 +++++++--- .../payments/ui/utils/CustomTabsHelper.kt | 6 +++--- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/xsolla-payments-sdk/build.gradle b/xsolla-payments-sdk/build.gradle index 737b86d0..cb6590f0 100644 --- a/xsolla-payments-sdk/build.gradle +++ b/xsolla-payments-sdk/build.gradle @@ -42,7 +42,7 @@ dependencies { implementation "androidx.browser:browser:$androidx_browser" - implementation "androidx.startup:startup-runtime:1.0.0" + implementation "androidx.startup:startup-runtime:1.1.1" } dokkaHtmlPartial { diff --git a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/XPayments.kt b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/XPayments.kt index d5c9f550..e54d6718 100644 --- a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/XPayments.kt +++ b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/XPayments.kt @@ -75,10 +75,6 @@ class XPayments { fun setPayStationVersion(version: PayStationVersion) = apply { this.payStationVersion = version } - private fun getPayStationVersion() = when (payStationVersion) { - PayStationVersion.V3 -> "paystation3" - PayStationVersion.V4 -> "paystation4" - } /** * Build the intent @@ -102,8 +98,8 @@ class XPayments { val uriBuilder = Uri.Builder() .scheme("https") .authority(getServer()) - .appendPath(getPayStationVersion()) - .appendQueryParameter("access_token", it.token) + .appendPath(getPayStationVersionPath()) + .appendQueryParameter(getTokenQueryParameterName(), it.token) appendAnalytics(uriBuilder) return uriBuilder.build().toString() @@ -125,6 +121,16 @@ class XPayments { if (AnalyticsUtils.gameEngineVersion.isNotBlank()) builder.appendQueryParameter("game_engine_v", AnalyticsUtils.gameEngineVersion) } + + private fun getPayStationVersionPath() = when (payStationVersion) { + PayStationVersion.V3 -> "paystation3" + PayStationVersion.V4 -> "paystation4" + } + + private fun getTokenQueryParameterName() = when (payStationVersion) { + PayStationVersion.V3 -> "access_token" + PayStationVersion.V4 -> "token" + } } /** diff --git a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCache.kt b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCache.kt index 8735f14e..491d08d2 100644 --- a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCache.kt +++ b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/caching/PayStationCache.kt @@ -16,6 +16,7 @@ import com.xsolla.android.payments.XPayments import com.xsolla.android.payments.ui.ActivityPayStation import com.xsolla.android.payments.ui.utils.BrowserUtils import com.xsolla.android.payments.ui.utils.CustomTabsHelper +import java.util.Locale class PayStationCache(val context: Context) { @@ -26,12 +27,15 @@ class PayStationCache(val context: Context) { fun init() { XPayments.createIntentBuilder(context) - + var locale = Locale.getDefault().language + if(locale.isEmpty()) locale = "en" if(BrowserUtils.isCustomTabsBrowserAvailable(context)) { - customTabHelper = CustomTabsHelper(context) + val payStation3WarmUpUrl = "https://secure.xsolla.com/paystation3/$locale/cache-warmup" + val payStation4WarmUpUrl = "https://secure.xsolla.com/paystation4/$locale/cache-warmup" + customTabHelper = CustomTabsHelper(context, payStation3WarmUpUrl, payStation4WarmUpUrl) customTabHelper.bindCustomTabsService() } else { - preloadUrl("https://secure.xsolla.com/paystation3/ru/cache-warmup") + preloadUrl("https://secure.xsolla.com/paystation4/$locale/cache-warmup") } } diff --git a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/CustomTabsHelper.kt b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/CustomTabsHelper.kt index b59b1069..b9c62698 100644 --- a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/CustomTabsHelper.kt +++ b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/utils/CustomTabsHelper.kt @@ -7,7 +7,7 @@ import androidx.browser.customtabs.CustomTabsClient import androidx.browser.customtabs.CustomTabsServiceConnection import androidx.browser.customtabs.CustomTabsSession -class CustomTabsHelper(private val context: Context) { +class CustomTabsHelper(private val context: Context, private val payStation3WarmUpUrl: String, private val payStation4WarmUpUrl: String) { private var customTabsSession: CustomTabsSession? = null private var mClient: CustomTabsClient? = null @@ -18,8 +18,8 @@ class CustomTabsHelper(private val context: Context) { mClient = client client.warmup(0) customTabsSession = client.newSession(null) - customTabsSession!!.mayLaunchUrl(Uri.parse("https://secure.xsolla.com/paystation3/ru/cache-warmup"), null, null) - customTabsSession!!.mayLaunchUrl(Uri.parse("https://secure.xsolla.com/paystation4/ru/cache-warmup"), null, null) + customTabsSession!!.mayLaunchUrl(Uri.parse(payStation3WarmUpUrl), null, null) + customTabsSession!!.mayLaunchUrl(Uri.parse(payStation4WarmUpUrl), null, null) } } From b6cb6287aa89dde6d6bdcaf9278687684c914419 Mon Sep 17 00:00:00 2001 From: Olga Salina Date: Thu, 25 Apr 2024 22:08:35 +0300 Subject: [PATCH 3/6] SDK-3651 Add loader --- .../android/payments/ui/ActivityPayStation.kt | 8 +++++++- .../xsolla_payments_activity_paystation.xml | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/ActivityPayStation.kt b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/ActivityPayStation.kt index 2032ac2e..84995a23 100644 --- a/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/ActivityPayStation.kt +++ b/xsolla-payments-sdk/src/main/java/com/xsolla/android/payments/ui/ActivityPayStation.kt @@ -19,6 +19,7 @@ import android.webkit.WebChromeClient import android.webkit.WebView import android.webkit.WebView.WebViewTransport import android.webkit.WebViewClient +import android.widget.FrameLayout import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import com.xsolla.android.payments.R @@ -41,6 +42,7 @@ internal class ActivityPayStation : AppCompatActivity() { private lateinit var url: String private lateinit var webView: WebView private lateinit var childWebView: WebView + private lateinit var loader: FrameLayout private lateinit var redirectScheme: String private lateinit var redirectHost: String @@ -82,6 +84,7 @@ internal class ActivityPayStation : AppCompatActivity() { setContentView(R.layout.xsolla_payments_activity_paystation) webView = findViewById(R.id.webview) childWebView = findViewById(R.id.childWebView) + loader = findViewById(R.id.loader) configureWebView() webView.loadUrl(url) } else { @@ -193,7 +196,10 @@ internal class ActivityPayStation : AppCompatActivity() { } super.doUpdateVisitedHistory(view, url, isReload) } - + override fun onPageFinished(view: WebView?, url: String?) { + loader.visibility = View.GONE + super.onPageFinished(view, url) + } } webView.setDownloadListener { url, userAgent, contentDisposition, mimeType, _ -> diff --git a/xsolla-payments-sdk/src/main/res/layout/xsolla_payments_activity_paystation.xml b/xsolla-payments-sdk/src/main/res/layout/xsolla_payments_activity_paystation.xml index e106af91..652a9d5a 100644 --- a/xsolla-payments-sdk/src/main/res/layout/xsolla_payments_activity_paystation.xml +++ b/xsolla-payments-sdk/src/main/res/layout/xsolla_payments_activity_paystation.xml @@ -15,4 +15,22 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:visibility="gone" /> + + + + + + From fab84f91aadedd674eff4eeb42b731751538554b Mon Sep 17 00:00:00 2001 From: Eric Vlasov Date: Thu, 2 May 2024 10:07:26 +0000 Subject: [PATCH 4/6] support for `with-geo` query parameter in `virtual_items` request --- .../java/com/xsolla/android/store/XStore.kt | 52 +++++++++++++++---- .../com/xsolla/android/store/api/StoreApi.kt | 3 +- .../response/items/VirtualItemsResponse.kt | 13 ++++- 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/xsolla-store-sdk/src/main/java/com/xsolla/android/store/XStore.kt b/xsolla-store-sdk/src/main/java/com/xsolla/android/store/XStore.kt index 1e86f128..839753c5 100644 --- a/xsolla-store-sdk/src/main/java/com/xsolla/android/store/XStore.kt +++ b/xsolla-store-sdk/src/main/java/com/xsolla/android/store/XStore.kt @@ -36,6 +36,7 @@ import retrofit2.Callback import retrofit2.Response import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import java.util.Locale class XStore private constructor( private val projectId: Int, @@ -1143,6 +1144,9 @@ class XStore private constructor( * The following languages are supported: Arabic (`ar`), Bulgarian (`bg`), Czech (`cs`), German (`de`), Spanish (`es`), French (`fr`), Hebrew (`he`), Italian (`it`), Japanese (`ja`), Korean (`ko`), Polish (`pl`), Portuguese (`pt`), Romanian (`ro`), Russian (`ru`), Thai (`th`), Turkish (`tr`), Vietnamese (`vi`), Chinese Simplified (`cn`), Chinese Traditional (`tw`), English (`en`, default). * @param additionalFields The list of additional fields. Available fields: `media_list`, `order`, `long_description`. * @param callback Status callback. + * @param requestGeoLocale If `TRUE` then requests the backend to send the deduced locale + * back with the response as [VirtualItemsResponse.geoLocale] based on user's current IP. + * **IMPORTANT:** Setting [country] argument will override the returned locale. * @see [More about the use cases](https://developers.xsolla.com/sdk/android/catalog/catalog-display/). */ @JvmStatic @@ -1153,16 +1157,19 @@ class XStore private constructor( offset: Int = 0, locale: String? = null, additionalFields: List? = listOf(), - country: String? = null + country: String? = null, + requestGeoLocale: Boolean? = null ) { - getInstance().storeApi.getVirtualItems( - getInstance().projectId, - limit, - offset, - locale, - additionalFields, - country - ) + getInstance().storeApi + .getVirtualItems( + getInstance().projectId, + limit, + offset, + locale, + additionalFields, + country, + withGeo = requestGeoLocale + ) .enqueue(object : Callback { override fun onResponse( call: Call, @@ -1171,7 +1178,32 @@ class XStore private constructor( if (response.isSuccessful) { val virtualItemsResponse = response.body() if (virtualItemsResponse != null) { - callback.onSuccess(virtualItemsResponse) + val geoLocale = requestGeoLocale?.takeIf { it }?.let { + val headers = response.headers() + + // Retrieve language and country ISO codes. + val localeCode = headers["X-User-Locale-Code"] + val countryCode = headers["X-User-Country-Code"] + + // If both ISO codes are present, only then attempt to parse those into a `Locale`. + if (!localeCode.isNullOrEmpty() && !countryCode.isNullOrEmpty()) { + try { + Locale(localeCode, countryCode) + } catch (_: NullPointerException) { + // We just ignore the locale altogether as it's not essential + // to the `VirtualItemsResponse`'s validity. Moreover, null + // pointer exception might only happen IF the code above is + // broken for some obscure reason, which shouldn't ever happen. + null + } + } else { + null + } + } + + callback.onSuccess( + virtualItemsResponse.copy(geoLocale = geoLocale) + ) } else { callback.onError(null, "Empty response") } diff --git a/xsolla-store-sdk/src/main/java/com/xsolla/android/store/api/StoreApi.kt b/xsolla-store-sdk/src/main/java/com/xsolla/android/store/api/StoreApi.kt index aa052680..60152579 100644 --- a/xsolla-store-sdk/src/main/java/com/xsolla/android/store/api/StoreApi.kt +++ b/xsolla-store-sdk/src/main/java/com/xsolla/android/store/api/StoreApi.kt @@ -255,7 +255,8 @@ internal interface StoreApi { @Query("offset") offset: Int, @Query("locale") locale: String?, @Query("additional_fields") additionalFields: List?, - @Query("country") country: String? + @Query("country") country: String?, + @Query("with_geo") withGeo: Boolean? ): Call @GET("/api/v2/project/{project_id}/items/virtual_items/all") diff --git a/xsolla-store-sdk/src/main/java/com/xsolla/android/store/entity/response/items/VirtualItemsResponse.kt b/xsolla-store-sdk/src/main/java/com/xsolla/android/store/entity/response/items/VirtualItemsResponse.kt index 56f0e7f5..167fcca7 100644 --- a/xsolla-store-sdk/src/main/java/com/xsolla/android/store/entity/response/items/VirtualItemsResponse.kt +++ b/xsolla-store-sdk/src/main/java/com/xsolla/android/store/entity/response/items/VirtualItemsResponse.kt @@ -5,9 +5,20 @@ import com.google.gson.annotations.SerializedName import com.xsolla.android.store.entity.response.common.* import kotlinx.parcelize.Parcelize import kotlinx.parcelize.RawValue +import java.util.Locale -data class VirtualItemsResponse(@SerializedName("has_more") val hasMore: Boolean = false, val items: List = emptyList()) { +data class VirtualItemsResponse( + /** + * An optional locale deduced and returned by the backend. + * + * Non-null only if the query parameter `with_geo=1` was added to the request. + */ + @Transient val geoLocale: Locale? = null, + @SerializedName("has_more") val hasMore: Boolean = false, + + val items: List = emptyList() +) { @Parcelize data class Item( val sku: String? = null, From 03ece884ea8861726e07c372c331d939cce6f19a Mon Sep 17 00:00:00 2001 From: Olga Salina Date: Mon, 6 May 2024 12:30:31 +0300 Subject: [PATCH 5/6] Update version and changelog --- CHANGELOG.md | 8 ++++++++ build.gradle | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 850fc1b1..30b3af40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,12 @@ # Changelog +## [2.5.6] - Store SDK - 2024-05-06 +### Changed +- `getVirtualItems` SDK method. Added the `requestGeoLocale` parameter. If `true`, the response returns the locale in the `geoLocale` parameter. + +## [1.3.4] - Payments SDK - 2024-05-06 +### Added +- Pay Station preloader. Allows faster content display in WebView and Custom Tabs. + ## [2.2.11] - Demo Apps - 2024-04-11 ### Fixed - Token refresh on application startup diff --git a/build.gradle b/build.gradle index 3f3f3d23..8a37f74c 100644 --- a/build.gradle +++ b/build.gradle @@ -2,8 +2,8 @@ buildscript { - ext.payments_sdk_version_name = '1.3.3' - ext.store_sdk_version_name = '2.5.5' + ext.payments_sdk_version_name = '1.3.4' + ext.store_sdk_version_name = '2.5.6' ext.inventory_sdk_version_name = '2.0.4' ext.login_sdk_version_name = '6.0.7' From 7356d852ebde0874bbc6c68432dbafaef7399cee Mon Sep 17 00:00:00 2001 From: Olga Salina Date: Mon, 6 May 2024 12:58:47 +0300 Subject: [PATCH 6/6] Update description --- .../src/main/java/com/xsolla/android/store/XStore.kt | 5 ++--- .../store/entity/response/items/VirtualItemsResponse.kt | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/xsolla-store-sdk/src/main/java/com/xsolla/android/store/XStore.kt b/xsolla-store-sdk/src/main/java/com/xsolla/android/store/XStore.kt index 839753c5..f5e96450 100644 --- a/xsolla-store-sdk/src/main/java/com/xsolla/android/store/XStore.kt +++ b/xsolla-store-sdk/src/main/java/com/xsolla/android/store/XStore.kt @@ -1144,9 +1144,8 @@ class XStore private constructor( * The following languages are supported: Arabic (`ar`), Bulgarian (`bg`), Czech (`cs`), German (`de`), Spanish (`es`), French (`fr`), Hebrew (`he`), Italian (`it`), Japanese (`ja`), Korean (`ko`), Polish (`pl`), Portuguese (`pt`), Romanian (`ro`), Russian (`ru`), Thai (`th`), Turkish (`tr`), Vietnamese (`vi`), Chinese Simplified (`cn`), Chinese Traditional (`tw`), English (`en`, default). * @param additionalFields The list of additional fields. Available fields: `media_list`, `order`, `long_description`. * @param callback Status callback. - * @param requestGeoLocale If `TRUE` then requests the backend to send the deduced locale - * back with the response as [VirtualItemsResponse.geoLocale] based on user's current IP. - * **IMPORTANT:** Setting [country] argument will override the returned locale. + * @param requestGeoLocale If `true`, requests the locale based on user's current IP. The backend returns the locale as [VirtualItemsResponse.geoLocale]. + * **IMPORTANT:** Setting [country] argument overrides the returned locale. * @see [More about the use cases](https://developers.xsolla.com/sdk/android/catalog/catalog-display/). */ @JvmStatic diff --git a/xsolla-store-sdk/src/main/java/com/xsolla/android/store/entity/response/items/VirtualItemsResponse.kt b/xsolla-store-sdk/src/main/java/com/xsolla/android/store/entity/response/items/VirtualItemsResponse.kt index 167fcca7..dd6abe24 100644 --- a/xsolla-store-sdk/src/main/java/com/xsolla/android/store/entity/response/items/VirtualItemsResponse.kt +++ b/xsolla-store-sdk/src/main/java/com/xsolla/android/store/entity/response/items/VirtualItemsResponse.kt @@ -9,9 +9,9 @@ import java.util.Locale data class VirtualItemsResponse( /** - * An optional locale deduced and returned by the backend. + * An optional locale returned by the backend based on user's current IP. * - * Non-null only if the query parameter `with_geo=1` was added to the request. + * Non-null only if request contains the `requestGeoLocale=true` query parameter. */ @Transient val geoLocale: Locale? = null,