diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppInitializer.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppInitializer.kt index b0344dd1421e..f55ce3b434fc 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppInitializer.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppInitializer.kt @@ -2,7 +2,6 @@ package com.woocommerce.android import android.app.Application import android.appwidget.AppWidgetManager -import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.ConnectivityManager @@ -10,10 +9,9 @@ import androidx.lifecycle.Lifecycle.State.STARTED import androidx.lifecycle.ProcessLifecycleOwner import com.automattic.android.experimentation.ExPlat import com.automattic.android.tracks.crashlogging.CrashLogging -import com.google.android.gms.common.ConnectionResult -import com.google.android.gms.common.GoogleApiAvailability import com.woocommerce.android.analytics.AnalyticsEvent import com.woocommerce.android.analytics.AnalyticsTracker +import com.woocommerce.android.applicationpasswords.ApplicationPasswordsNotifier import com.woocommerce.android.di.AppCoroutineScope import com.woocommerce.android.network.ConnectionChangeReceiver import com.woocommerce.android.push.RegisterDevice @@ -23,6 +21,7 @@ import com.woocommerce.android.support.ZendeskHelper import com.woocommerce.android.tools.NetworkStatus import com.woocommerce.android.tools.RateLimitedTask import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.tools.SiteConnectionType import com.woocommerce.android.tracker.SendTelemetry import com.woocommerce.android.ui.appwidgets.getWidgetName import com.woocommerce.android.ui.common.UserEligibilityFetcher @@ -87,6 +86,7 @@ class AppInitializer @Inject constructor() : ApplicationLifecycleListener { @Inject lateinit var siteObserver: SiteObserver @Inject lateinit var wooLog: WooLogWrapper @Inject lateinit var registerDevice: RegisterDevice + @Inject lateinit var applicationPasswordsNotifier: ApplicationPasswordsNotifier @Inject lateinit var explat: ExPlat @@ -113,7 +113,7 @@ class AppInitializer @Inject constructor() : ApplicationLifecycleListener { ProcessLifecycleOwner.get().lifecycle.currentState.isAtLeast(STARTED) ) { // The previously selected site is not connected anymore, take the user to the site picker - WooLog.w(T.SITE_PICKER, "Selected site no longer has WooCommerce") + WooLog.w(T.LOGIN, "Selected site no longer has WooCommerce") selectedSite.reset() openMainActivity() } @@ -169,6 +169,8 @@ class AppInitializer @Inject constructor() : ApplicationLifecycleListener { appCoroutineScope.launch { siteObserver.observeAndUpdateSelectedSiteData() } + + monitorApplicationPasswordsStatus() } @Suppress("DEPRECATION") @@ -232,18 +234,15 @@ class AppInitializer @Inject constructor() : ApplicationLifecycleListener { application.startActivity(intent) } - private fun isGooglePlayServicesAvailable(context: Context): Boolean { - val googleApiAvailability = GoogleApiAvailability.getInstance() - - return when (val connectionResult = googleApiAvailability.isGooglePlayServicesAvailable(context)) { - ConnectionResult.SUCCESS -> true - else -> { - WooLog.w( - T.NOTIFS, - "Google Play Services unavailable, connection result: " + - googleApiAvailability.getErrorString(connectionResult) - ) - return false + private fun monitorApplicationPasswordsStatus() { + appCoroutineScope.launch { + // Log user out if the Application Passwords feature gets disabled + applicationPasswordsNotifier.featureUnavailableEvents.collect { + if (selectedSite.connectionType == SiteConnectionType.ApplicationPasswords) { + WooLog.w(T.LOGIN, "Application Passwords support has been disabled in the current site") + selectedSite.reset() + openMainActivity() + } } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/error/ApplicationPasswordsDisabledDialogFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/error/ApplicationPasswordsDisabledDialogFragment.kt new file mode 100644 index 000000000000..65ef9f683b4e --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/error/ApplicationPasswordsDisabledDialogFragment.kt @@ -0,0 +1,45 @@ +package com.woocommerce.android.ui.login.error + +import androidx.core.os.bundleOf +import androidx.fragment.app.setFragmentResult +import com.woocommerce.android.R +import com.woocommerce.android.support.help.HelpOrigin +import com.woocommerce.android.ui.login.error.base.LoginBaseErrorDialogFragment +import com.woocommerce.android.util.ChromeCustomTabUtils + +class ApplicationPasswordsDisabledDialogFragment : LoginBaseErrorDialogFragment() { + companion object { + const val RETRY_RESULT = "retry" + private const val SITE_URL_KEY = "site-url" + private const val APPLICATION_PASSWORDS_GUIDE = + "https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/" + + fun newInstance(siteUrl: String) = ApplicationPasswordsDisabledDialogFragment().apply { + arguments = bundleOf(SITE_URL_KEY to siteUrl) + } + } + + override val text: CharSequence + get() = getString(R.string.login_application_passwords_unavailable, requireArguments().getString(SITE_URL_KEY)) + override val helpOrigin: HelpOrigin + get() = HelpOrigin.LOGIN_SITE_ADDRESS + + override val inlineButtons: List + get() = listOf( + LoginErrorButton( + title = R.string.login_application_passwords_help, + onClick = { + ChromeCustomTabUtils.launchUrl(requireContext(), APPLICATION_PASSWORDS_GUIDE) + } + ) + ) + + override val primaryButton: LoginErrorButton + get() = LoginErrorButton( + title = R.string.retry, + onClick = { + setFragmentResult(RETRY_RESULT, bundleOf()) + dismiss() + } + ) +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/sitecredentials/LoginSiteCredentialsFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/sitecredentials/LoginSiteCredentialsFragment.kt index 958bc2904dee..b987468cfd3d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/sitecredentials/LoginSiteCredentialsFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/sitecredentials/LoginSiteCredentialsFragment.kt @@ -11,8 +11,10 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import com.woocommerce.android.ui.base.UIMessageResolver import com.woocommerce.android.ui.compose.theme.WooThemeWithBackground +import com.woocommerce.android.ui.login.error.ApplicationPasswordsDisabledDialogFragment import com.woocommerce.android.ui.login.error.notwoo.LoginNotWooDialogFragment import com.woocommerce.android.ui.login.sitecredentials.LoginSiteCredentialsViewModel.LoggedIn +import com.woocommerce.android.ui.login.sitecredentials.LoginSiteCredentialsViewModel.ShowApplicationPasswordsUnavailableScreen import com.woocommerce.android.ui.login.sitecredentials.LoginSiteCredentialsViewModel.ShowNonWooErrorScreen import com.woocommerce.android.ui.login.sitecredentials.LoginSiteCredentialsViewModel.ShowResetPasswordScreen import com.woocommerce.android.viewmodel.MultiLiveEvent.Event.Exit @@ -69,6 +71,9 @@ class LoginSiteCredentialsFragment : Fragment() { is ShowResetPasswordScreen -> loginListener.forgotPassword(it.siteAddress) is ShowNonWooErrorScreen -> LoginNotWooDialogFragment.newInstance(it.siteAddress) .show(childFragmentManager, LoginNotWooDialogFragment.TAG) + is ShowApplicationPasswordsUnavailableScreen -> + ApplicationPasswordsDisabledDialogFragment.newInstance(it.siteAddress) + .show(childFragmentManager, LoginNotWooDialogFragment.TAG) is ShowSnackbar -> uiMessageResolver.showSnack(it.message) is ShowUiStringSnackbar -> uiMessageResolver.showSnack(it.message) is Exit -> requireActivity().onBackPressedDispatcher.onBackPressed() @@ -83,5 +88,12 @@ class LoginSiteCredentialsFragment : Fragment() { ) { _, _ -> viewModel.onWooInstallationAttempted() } + + childFragmentManager.setFragmentResultListener( + ApplicationPasswordsDisabledDialogFragment.RETRY_RESULT, + viewLifecycleOwner + ) { _, _ -> + viewModel.retryApplicationPasswordsCheck() + } } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/sitecredentials/LoginSiteCredentialsViewModel.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/sitecredentials/LoginSiteCredentialsViewModel.kt index e07328f2ecbe..53fe5d940ea0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/sitecredentials/LoginSiteCredentialsViewModel.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/login/sitecredentials/LoginSiteCredentialsViewModel.kt @@ -7,6 +7,7 @@ import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope import com.woocommerce.android.OnChangedException import com.woocommerce.android.R +import com.woocommerce.android.applicationpasswords.ApplicationPasswordsNotifier import com.woocommerce.android.model.UiString.UiStringRes import com.woocommerce.android.model.UiString.UiStringText import com.woocommerce.android.tools.SelectedSite @@ -22,7 +23,9 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize @@ -46,7 +49,8 @@ class LoginSiteCredentialsViewModel @Inject constructor( private val wpApiSiteRepository: WPApiSiteRepository, private val selectedSite: SelectedSite, private val loginAnalyticsListener: LoginAnalyticsListener, - private val resourceProvider: ResourceProvider + private val resourceProvider: ResourceProvider, + applicationPasswordsNotifier: ApplicationPasswordsNotifier ) : ScopedViewModel(savedStateHandle) { companion object { const val SITE_ADDRESS_KEY = "site-address" @@ -77,6 +81,11 @@ class LoginSiteCredentialsViewModel @Inject constructor( init { loginAnalyticsListener.trackUsernamePasswordFormViewed() + applicationPasswordsNotifier.featureUnavailableEvents + .onEach { + triggerEvent(ShowApplicationPasswordsUnavailableScreen(siteAddress)) + } + .launchIn(this) } fun onUsernameChanged(username: String) { @@ -102,6 +111,7 @@ class LoginSiteCredentialsViewModel @Inject constructor( checkWooStatus(it) }, onFailure = { exception -> + isLoading.value = false var errorMessage: Int? = null if (exception is OnChangedException && exception.error is AuthenticationError) { errorMessage = exception.error.toErrorMessage() @@ -127,10 +137,10 @@ class LoginSiteCredentialsViewModel @Inject constructor( ) } ) - isLoading.value = false } private suspend fun checkWooStatus(site: SiteModel) { + isLoading.value = true wpApiSiteRepository.checkWooStatus(site = site).fold( onSuccess = { isWooInstalled -> if (isWooInstalled) { @@ -145,6 +155,7 @@ class LoginSiteCredentialsViewModel @Inject constructor( triggerEvent(ShowSnackbar(R.string.error_generic)) } ) + isLoading.value = false } fun onResetPasswordClick() { @@ -156,9 +167,11 @@ class LoginSiteCredentialsViewModel @Inject constructor( } fun onWooInstallationAttempted() = launch { - isLoading.value = true checkWooStatus(wpApiSiteRepository.getSiteByUrl(siteAddress)!!) - isLoading.value = false + } + + fun retryApplicationPasswordsCheck() = launch { + checkWooStatus(wpApiSiteRepository.getSiteByUrl(siteAddress)!!) } private fun String.removeSchemeAndSuffix() = UrlUtils.removeScheme(UrlUtils.removeXmlrpcSuffix(this)) @@ -193,4 +206,5 @@ class LoginSiteCredentialsViewModel @Inject constructor( data class LoggedIn(val localSiteId: Int) : MultiLiveEvent.Event() data class ShowResetPasswordScreen(val siteAddress: String) : MultiLiveEvent.Event() data class ShowNonWooErrorScreen(val siteAddress: String) : MultiLiveEvent.Event() + data class ShowApplicationPasswordsUnavailableScreen(val siteAddress: String) : MultiLiveEvent.Event() } diff --git a/WooCommerce/src/main/res/values/strings.xml b/WooCommerce/src/main/res/values/strings.xml index 2085e7412cbb..ef60d23ada97 100644 --- a/WooCommerce/src/main/res/values/strings.xml +++ b/WooCommerce/src/main/res/values/strings.xml @@ -335,6 +335,7 @@ Try connecting again to access your store. Continue connection Exit Without Connecting + It looks like Application Passwords feature is disabled in your site %1$s.\n Please enable it to use the WooCommerce app.