Skip to content

Commit 2f9b61b

Browse files
authored
Merge pull request #7924 from woocommerce/feature/check-iap-eligibility
Feature/check iap eligibility
2 parents 420475e + a2ba994 commit 2f9b61b

File tree

13 files changed

+307
-20
lines changed

13 files changed

+307
-20
lines changed

WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsEvent.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,9 +769,11 @@ enum class AnalyticsEvent(val siteless: Boolean = false) {
769769
SITE_CREATION_SITE_PREVIEWED,
770770
SITE_CREATION_STORE_MANAGEMENT_OPENED,
771771
SITE_CREATION_STEP(siteless = true),
772+
SITE_CREATION_IAP_ELIGIBILITY(siteless = true),
773+
SITE_CREATION_IAP_ERROR(siteless = true),
772774

773775
APPLICATION_PASSWORDS_NEW_PASSWORD_CREATED,
774776
APPLICATION_PASSWORDS_TEST_INITIATED,
775777
APPLICATION_PASSWORDS_NOT_AVAILABLE,
776-
APPLICATION_PASSWORDS_AVAILABLE
778+
APPLICATION_PASSWORDS_AVAILABLE,
777779
}

WooCommerce/src/main/kotlin/com/woocommerce/android/analytics/AnalyticsTracker.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ class AnalyticsTracker private constructor(private val context: Context) {
425425
const val KEY_PATH = "path"
426426

427427
// -- Store creation
428+
const val KEY_IAP_ELIGIBLE = "is_eligible"
428429
const val VALUE_LOGIN_EMAIL_ERROR = "login_email_error"
429430
const val VALUE_SWITCHING_STORE = "switching_store"
430431
const val VALUE_PROLOGUE = "prologue"
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.woocommerce.android.di
2+
3+
import android.app.Application
4+
import com.woocommerce.android.iap.pub.IAPSitePurchasePlanFactory
5+
import com.woocommerce.android.iap.pub.PurchaseWPComPlanActions
6+
import com.woocommerce.android.iap.pub.PurchaseWpComPlanSupportChecker
7+
import com.woocommerce.android.iapshowcase.purchase.IAPShowcaseMobilePayAPIProvider
8+
import com.woocommerce.android.ui.login.storecreation.iap.WooIapLogWrapper
9+
import dagger.Module
10+
import dagger.Provides
11+
import dagger.hilt.InstallIn
12+
import dagger.hilt.components.SingletonComponent
13+
14+
@InstallIn(SingletonComponent::class)
15+
@Module
16+
class InAppPurchasesModule {
17+
@Provides
18+
fun providePurchaseWPComPlanActions(
19+
context: Application,
20+
mobilePayAPIProvider: IAPShowcaseMobilePayAPIProvider
21+
): PurchaseWPComPlanActions =
22+
IAPSitePurchasePlanFactory.createIAPSitePurchasePlan(
23+
context,
24+
1L,
25+
WooIapLogWrapper(),
26+
mobilePayAPIProvider::buildMobilePayAPI
27+
)
28+
29+
@Provides
30+
fun providePurchaseWpComPlanSupportChecker(application: Application): PurchaseWpComPlanSupportChecker =
31+
IAPSitePurchasePlanFactory.createIAPPurchaseWpComPlanSupportChecker(
32+
application,
33+
WooIapLogWrapper(),
34+
)
35+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.woocommerce.android.ui.login.storecreation.iap
2+
3+
import android.os.Bundle
4+
import android.view.LayoutInflater
5+
import android.view.View
6+
import android.view.ViewGroup
7+
import androidx.compose.ui.platform.ComposeView
8+
import androidx.compose.ui.platform.ViewCompositionStrategy
9+
import androidx.fragment.app.viewModels
10+
import androidx.navigation.fragment.findNavController
11+
import com.woocommerce.android.extensions.navigateSafely
12+
import com.woocommerce.android.ui.base.BaseFragment
13+
import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground
14+
import com.woocommerce.android.ui.login.storecreation.iap.IapEligibilityViewModel.IapEligibilityEvent.NavigateToNextStep
15+
import com.woocommerce.android.ui.login.storecreation.iap.IapEligibilityViewModel.IapEligibilityEvent.NavigateToWebStoreCreation
16+
import com.woocommerce.android.ui.main.AppBarStatus
17+
import com.woocommerce.android.viewmodel.MultiLiveEvent
18+
import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit
19+
import dagger.hilt.android.AndroidEntryPoint
20+
21+
@AndroidEntryPoint
22+
class CheckIapEligibilityFragment : BaseFragment() {
23+
private val viewModel: IapEligibilityViewModel by viewModels()
24+
25+
override val activityAppBarStatus: AppBarStatus
26+
get() = AppBarStatus.Hidden
27+
28+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
29+
return ComposeView(requireContext()).apply {
30+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
31+
setContent {
32+
WooThemeWithBackground {
33+
IapEligibilityScreen(viewModel = viewModel)
34+
}
35+
}
36+
}
37+
}
38+
39+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
40+
super.onViewCreated(view, savedInstanceState)
41+
setupObservers()
42+
viewModel.checkIapEligibility()
43+
}
44+
45+
private fun setupObservers() {
46+
viewModel.event.observe(viewLifecycleOwner) { event ->
47+
when (event) {
48+
is NavigateToWebStoreCreation -> navigateToStoreCreationWeb()
49+
is NavigateToNextStep -> navigateToStoreCreationNative()
50+
is Exit -> findNavController().popBackStack()
51+
is MultiLiveEvent.Event.ShowDialog -> event.showDialog()
52+
}
53+
}
54+
}
55+
56+
private fun navigateToStoreCreationWeb() {
57+
findNavController()
58+
.navigateSafely(
59+
CheckIapEligibilityFragmentDirections.actionCheckIapEligibilityFragmentToWebViewStoreCreationFragment()
60+
)
61+
}
62+
63+
private fun navigateToStoreCreationNative() {
64+
findNavController()
65+
.navigateSafely(
66+
CheckIapEligibilityFragmentDirections.actionCheckIapEligibilityFragmentToStoreNamePickerFragment(),
67+
skipThrottling = true
68+
)
69+
}
70+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.woocommerce.android.ui.login.storecreation.iap
2+
3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Column
5+
import androidx.compose.foundation.layout.fillMaxSize
6+
import androidx.compose.foundation.layout.padding
7+
import androidx.compose.material.CircularProgressIndicator
8+
import androidx.compose.material.MaterialTheme
9+
import androidx.compose.material.Text
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.livedata.observeAsState
12+
import androidx.compose.ui.Alignment
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.res.colorResource
15+
import androidx.compose.ui.res.dimensionResource
16+
import androidx.compose.ui.res.stringResource
17+
import androidx.compose.ui.text.style.TextAlign
18+
import com.woocommerce.android.R
19+
20+
@Composable
21+
fun IapEligibilityScreen(viewModel: IapEligibilityViewModel) {
22+
viewModel.isCheckingIapEligibility.observeAsState().value?.let { isLoading ->
23+
Column(
24+
modifier = Modifier
25+
.fillMaxSize()
26+
.padding(dimensionResource(id = R.dimen.major_100)),
27+
verticalArrangement = Arrangement.Center,
28+
horizontalAlignment = Alignment.CenterHorizontally
29+
) {
30+
if (isLoading)
31+
CircularProgressIndicator()
32+
Text(
33+
modifier = Modifier.padding(top = dimensionResource(id = R.dimen.major_200)),
34+
text =
35+
stringResource(
36+
id = if (isLoading) R.string.store_creation_iap_eligibility_loading_title
37+
else R.string.store_creation_iap_eligibility_check_error_title
38+
),
39+
style = MaterialTheme.typography.h6,
40+
textAlign = TextAlign.Center,
41+
)
42+
if (isLoading)
43+
Text(
44+
modifier = Modifier.padding(top = dimensionResource(id = R.dimen.major_100)),
45+
text = stringResource(id = R.string.store_creation_iap_eligibility_loading_subtitle),
46+
style = MaterialTheme.typography.subtitle1,
47+
color = colorResource(id = R.color.color_on_surface_medium)
48+
)
49+
}
50+
}
51+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.woocommerce.android.ui.login.storecreation.iap
2+
3+
import androidx.lifecycle.LiveData
4+
import androidx.lifecycle.SavedStateHandle
5+
import androidx.lifecycle.asLiveData
6+
import com.woocommerce.android.R
7+
import com.woocommerce.android.analytics.AnalyticsEvent
8+
import com.woocommerce.android.analytics.AnalyticsTracker
9+
import com.woocommerce.android.analytics.AnalyticsTrackerWrapper
10+
import com.woocommerce.android.iap.pub.PurchaseWpComPlanSupportChecker
11+
import com.woocommerce.android.iap.pub.model.IAPSupportedResult
12+
import com.woocommerce.android.ui.login.storecreation.iap.IapEligibilityViewModel.IapEligibilityEvent.NavigateToNextStep
13+
import com.woocommerce.android.util.FeatureFlag
14+
import com.woocommerce.android.viewmodel.MultiLiveEvent
15+
import com.woocommerce.android.viewmodel.ScopedViewModel
16+
import com.woocommerce.android.viewmodel.getStateFlow
17+
import dagger.hilt.android.lifecycle.HiltViewModel
18+
import kotlinx.coroutines.launch
19+
import javax.inject.Inject
20+
21+
@HiltViewModel
22+
class IapEligibilityViewModel @Inject constructor(
23+
savedStateHandle: SavedStateHandle,
24+
private val planSupportChecker: PurchaseWpComPlanSupportChecker,
25+
private val analyticsTrackerWrapper: AnalyticsTrackerWrapper
26+
) : ScopedViewModel(savedStateHandle) {
27+
private val _isCheckingIapEligibility = savedState.getStateFlow(scope = this, initialValue = true)
28+
val isCheckingIapEligibility: LiveData<Boolean> = _isCheckingIapEligibility.asLiveData()
29+
30+
fun checkIapEligibility() {
31+
if (FeatureFlag.IAP_FOR_STORE_CREATION.isEnabled()) {
32+
launch {
33+
when (val result = planSupportChecker.isIAPSupported()) {
34+
is IAPSupportedResult.Success -> onSuccess(result)
35+
is IAPSupportedResult.Error -> onError(result)
36+
}
37+
}
38+
} else {
39+
triggerEvent(NavigateToNextStep)
40+
}
41+
}
42+
43+
private fun onError(result: IAPSupportedResult.Error) {
44+
analyticsTrackerWrapper.track(
45+
AnalyticsEvent.SITE_CREATION_IAP_ERROR,
46+
mapOf(AnalyticsTracker.KEY_ERROR_TYPE to result.errorType.toString())
47+
)
48+
onUserNotEligibleForIAP()
49+
}
50+
51+
private fun onUserNotEligibleForIAP() {
52+
_isCheckingIapEligibility.value = false
53+
triggerEvent(
54+
MultiLiveEvent.Event.ShowDialog(
55+
titleId = R.string.store_creation_iap_eligibility_check_error_title,
56+
messageId = R.string.store_creation_iap_eligibility_check_error_description,
57+
negativeBtnAction = { dialog, _ ->
58+
triggerEvent(MultiLiveEvent.Event.Exit)
59+
dialog.dismiss()
60+
},
61+
negativeButtonId = R.string.close
62+
)
63+
)
64+
}
65+
66+
private fun onSuccess(result: IAPSupportedResult.Success) {
67+
analyticsTrackerWrapper.track(
68+
AnalyticsEvent.SITE_CREATION_IAP_ELIGIBILITY,
69+
mapOf(AnalyticsTracker.KEY_IAP_ELIGIBLE to result.isSupported)
70+
)
71+
when {
72+
result.isSupported -> triggerEvent(NavigateToNextStep)
73+
else -> onUserNotEligibleForIAP()
74+
}
75+
}
76+
77+
sealed class IapEligibilityEvent : MultiLiveEvent.Event() {
78+
object NavigateToNextStep : IapEligibilityEvent()
79+
object NavigateToWebStoreCreation : IapEligibilityEvent()
80+
}
81+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.woocommerce.android.ui.login.storecreation.iap
2+
3+
import com.woocommerce.android.iap.pub.IAPLogWrapper
4+
import com.woocommerce.android.util.WooLog
5+
import javax.inject.Inject
6+
7+
class WooIapLogWrapper @Inject constructor() : IAPLogWrapper {
8+
override fun w(tag: String, message: String) {
9+
WooLog.w(WooLog.T.IAP, message)
10+
}
11+
12+
override fun d(tag: String, message: String) {
13+
WooLog.d(WooLog.T.IAP, message)
14+
}
15+
16+
override fun e(tag: String, message: String) {
17+
WooLog.e(WooLog.T.IAP, message)
18+
}
19+
}

WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ enum class FeatureFlag {
1515
ORDER_CREATION_CUSTOMER_SEARCH,
1616
NATIVE_STORE_CREATION_FLOW,
1717
STORE_PROFILER_FLOW,
18-
REST_API;
18+
REST_API,
19+
IAP_FOR_STORE_CREATION;
1920

2021
fun isEnabled(context: Context? = null): Boolean {
2122
return when (this) {
@@ -26,14 +27,14 @@ enum class FeatureFlag {
2627
COUPONS_M2,
2728
JETPACK_CP,
2829
ORDER_CREATION_CUSTOMER_SEARCH,
29-
UNIFIED_ORDER_EDITING -> true
30+
UNIFIED_ORDER_EDITING,
31+
NATIVE_STORE_CREATION_FLOW -> true
3032

3133
MORE_MENU_INBOX,
3234
WC_SHIPPING_BANNER,
3335
STORE_PROFILER_FLOW,
34-
REST_API -> PackageUtils.isDebugBuild()
35-
36-
NATIVE_STORE_CREATION_FLOW -> true
36+
REST_API,
37+
IAP_FOR_STORE_CREATION -> PackageUtils.isDebugBuild()
3738
}
3839
}
3940
}

WooCommerce/src/main/kotlin/com/woocommerce/android/util/WooLog.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ object WooLog {
3131
COUPONS,
3232
JITM,
3333
PLUGINS,
34+
IAP
3435
}
3536

3637
// Breaking convention to be consistent with org.wordpress.android.util.AppLog

WooCommerce/src/main/res/navigation/nav_graph_site_picker.xml

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
android:id="@+id/nav_graph_site_picker"
55
app:startDestination="@id/sitePickerFragment">
66

7-
<include app:graph="@navigation/nav_graph_store_creation" />
7+
<include app:graph="@navigation/nav_graph_store_creation_native" />
8+
<include app:graph="@navigation/nav_graph_store_creation_web" />
89
<include app:graph="@navigation/nav_graph_jetpack_activation" />
910

1011
<fragment
@@ -31,10 +32,10 @@
3132
app:destination="@id/addStoreBottomSheetFragment" />
3233
<action
3334
android:id="@+id/action_sitePickerFragment_to_webViewStoreCreationFragment"
34-
app:destination="@id/webViewStoreCreationFragment" />
35+
app:destination="@id/nav_graph_store_creation_web" />
3536
<action
3637
android:id="@+id/action_sitePickerFragment_to_storeCreationNativeFlow"
37-
app:destination="@id/nav_graph_store_creation"/>
38+
app:destination="@id/nav_graph_store_creation" />
3839
</fragment>
3940
<fragment
4041
android:id="@+id/sitePickerSiteDiscoveryFragment"
@@ -45,7 +46,7 @@
4546
app:destination="@id/accountMismatchErrorFragment" />
4647
<action
4748
android:id="@+id/action_sitePickerSiteDiscoveryFragment_to_jetpackActivation"
48-
app:destination="@id/nav_graph_jetpack_activation" >
49+
app:destination="@id/nav_graph_jetpack_activation">
4950
<argument
5051
android:name="siteUrl"
5152
app:argType="string" />
@@ -73,10 +74,6 @@
7374
android:defaultValue="true"
7475
app:argType="boolean" />
7576
</fragment>
76-
<fragment
77-
android:id="@+id/webViewStoreCreationFragment"
78-
android:name="com.woocommerce.android.ui.login.storecreation.webview.WebViewStoreCreationFragment"
79-
android:label="WebViewStoreCreationFragment" />
8077
<dialog
8178
android:id="@+id/addStoreBottomSheetFragment"
8279
android:name="com.woocommerce.android.ui.sitepicker.AddStoreBottomSheetFragment"
@@ -86,7 +83,7 @@
8683
app:destination="@id/sitePickerSiteDiscoveryFragment" />
8784
<action
8885
android:id="@+id/action_addStoreBottomSheetFragment_to_webViewStoreCreationFragment"
89-
app:destination="@id/webViewStoreCreationFragment" />
86+
app:destination="@id/nav_graph_store_creation_web" />
9087
<action
9188
android:id="@+id/action_addStoreBottomSheetFragment_to_storeCreationNativeFlow"
9289
app:destination="@id/nav_graph_store_creation" />

0 commit comments

Comments
 (0)