diff --git a/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/ApplicationPasswordsModule.kt b/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/ApplicationPasswordsModule.kt new file mode 100644 index 000000000000..b8b819064155 --- /dev/null +++ b/WooCommerce-Wear/src/main/java/com/woocommerce/android/wear/di/ApplicationPasswordsModule.kt @@ -0,0 +1,24 @@ +package com.woocommerce.android.wear.di + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.ApplicationPasswordsConfiguration +import javax.inject.Inject + +@Module +@InstallIn(SingletonComponent::class) +interface ApplicationPasswordsModule { + @Binds + fun bindApplicationPasswordsConfiguration( + configuration: WooWearApplicationPasswordsConfiguration + ): ApplicationPasswordsConfiguration +} + +class WooWearApplicationPasswordsConfiguration @Inject constructor() : ApplicationPasswordsConfiguration { + override val applicationName: String = "" + + override fun isEnabledForDirectAccess(): Boolean = false + override suspend fun isEnabledForJetpackAccess(): Boolean = false +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt index 9f2974d0dcc2..ec1271d6e5a3 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefs.kt @@ -134,7 +134,8 @@ object AppPrefs { CUSTOM_FIELDS_TOP_BANNER_DISMISSED, BLAZE_CAMPAIGN_SELECTED_OBJECTIVE, BLAZE_CAMPAIGN_OBJECTIVE_SWITCH_CHECKED, - IS_SITE_WPCOM_SUSPENDED + IS_SITE_WPCOM_SUSPENDED, + JETPACK_APP_PASSWORDS_ENABLED } /** @@ -301,6 +302,10 @@ object AppPrefs { get() = getBoolean(DeletablePrefKey.GATEWAY_MIGRATED, false) set(value) = setBoolean(DeletablePrefKey.GATEWAY_MIGRATED, value) + var jetpackAppPasswordsEnabled: Boolean + get() = getBoolean(DeletablePrefKey.JETPACK_APP_PASSWORDS_ENABLED, true) + set(value) = setBoolean(DeletablePrefKey.JETPACK_APP_PASSWORDS_ENABLED, value) + fun getProductSortingChoice(currentSiteId: Int) = getString(getProductSortingKey(currentSiteId)).orNullIfEmpty() fun setProductSortingChoice(currentSiteId: Int, value: String) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt index 3f7cdfea83ed..ea1a6ad058d0 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/AppPrefsWrapper.kt @@ -42,6 +42,8 @@ open class AppPrefsWrapper @Inject constructor() { open var orderSummaryMigrated by AppPrefs::orderSummaryMigrated open var gatewayMigrated by AppPrefs::gatewayMigrated + var jetpackAppPasswordsEnabled by AppPrefs::jetpackAppPasswordsEnabled + fun getAppInstallationDate() = AppPrefs.installationDate fun getReceiptUrl(localSiteId: Int, remoteSiteId: Long, selfHostedSiteId: Long, orderId: Long) = diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/applicationpasswords/WooApplicationPasswordsConfiguration.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/applicationpasswords/WooApplicationPasswordsConfiguration.kt new file mode 100644 index 000000000000..8de9a63f32b3 --- /dev/null +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/applicationpasswords/WooApplicationPasswordsConfiguration.kt @@ -0,0 +1,18 @@ +package com.woocommerce.android.applicationpasswords + +import com.woocommerce.android.AppPrefsWrapper +import com.woocommerce.android.BuildConfig +import com.woocommerce.android.util.DeviceInfo +import com.woocommerce.android.util.FeatureFlag.APP_PASSWORDS_FOR_JETPACK_SITES +import jakarta.inject.Inject +import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.ApplicationPasswordsConfiguration + +class WooApplicationPasswordsConfiguration @Inject constructor( + private val appPrefs: AppPrefsWrapper +) : ApplicationPasswordsConfiguration { + override val applicationName: String = + "${BuildConfig.APPLICATION_ID}.app-client.${DeviceInfo.name.replace(' ', '-')}" + + override suspend fun isEnabledForJetpackAccess(): Boolean = + APP_PASSWORDS_FOR_JETPACK_SITES.isEnabled() && appPrefs.jetpackAppPasswordsEnabled +} diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/config/FirebaseRemoteConfigRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/config/FirebaseRemoteConfigRepository.kt index 7b5b9d20f420..dc843451b652 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/config/FirebaseRemoteConfigRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/config/FirebaseRemoteConfigRepository.kt @@ -21,8 +21,6 @@ class FirebaseRemoteConfigRepository @Inject constructor( companion object { private const val DEBUG_INTERVAL = 10L private const val RELEASE_INTERVAL = 31200L - - const val KEY_ENABLE_JETPACK_APP_PASSWORDS_EXPERIMENT = "wcandroid_enable_jetpack_app_passwords_experiment_2" } private val minimumFetchIntervalInSeconds = @@ -37,11 +35,7 @@ class FirebaseRemoteConfigRepository @Inject constructor( private val _fetchStatus = MutableStateFlow(RemoteConfigFetchStatus.Pending) override val fetchStatus: Flow = _fetchStatus.asStateFlow() - private val defaultValues by lazy { - mapOf( - KEY_ENABLE_JETPACK_APP_PASSWORDS_EXPERIMENT to false.toString() - ) - } + private val defaultValues = emptyMap() init { remoteConfig.apply { @@ -73,8 +67,4 @@ class FirebaseRemoteConfigRepository @Inject constructor( @VisibleForTesting fun observeStringRemoteValue(key: String) = changesTrigger .map { remoteConfig.getString(key) } - - override fun isJetpackAppPasswordsExperimentEnabled(): Boolean { - return remoteConfig.getBoolean(KEY_ENABLE_JETPACK_APP_PASSWORDS_EXPERIMENT) - } } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/config/RemoteConfigRepository.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/config/RemoteConfigRepository.kt index d77bf3b3be66..38c2656d0d62 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/config/RemoteConfigRepository.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/config/RemoteConfigRepository.kt @@ -5,8 +5,6 @@ import kotlinx.coroutines.flow.Flow interface RemoteConfigRepository { val fetchStatus: Flow fun fetchRemoteConfig() - - fun isJetpackAppPasswordsExperimentEnabled(): Boolean } enum class RemoteConfigFetchStatus { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/ApplicationPasswordsModule.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/ApplicationPasswordsModule.kt index 55ec5a7e5a88..edff7e3ca044 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/di/ApplicationPasswordsModule.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/di/ApplicationPasswordsModule.kt @@ -1,14 +1,12 @@ package com.woocommerce.android.di -import com.woocommerce.android.BuildConfig import com.woocommerce.android.applicationpasswords.ApplicationPasswordsNotifier -import com.woocommerce.android.util.DeviceInfo +import com.woocommerce.android.applicationpasswords.WooApplicationPasswordsConfiguration import dagger.Binds import dagger.Module -import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent -import org.wordpress.android.fluxc.module.ApplicationPasswordsClientId +import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.ApplicationPasswordsConfiguration import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.ApplicationPasswordsListener @Module @@ -19,10 +17,8 @@ interface ApplicationPasswordsModule { notifier: ApplicationPasswordsNotifier ): ApplicationPasswordsListener - companion object { - @Provides - @ApplicationPasswordsClientId - fun providesApplicationPasswordClientId() = - "${BuildConfig.APPLICATION_ID}.app-client.${DeviceInfo.name.replace(' ', '-')}" - } + @Binds + fun bindApplicationPasswordsConfiguration( + configuration: WooApplicationPasswordsConfiguration + ): ApplicationPasswordsConfiguration } 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 121c57ba5399..f91a0332d050 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 @@ -37,8 +37,8 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.wordpress.android.fluxc.model.SiteModel -import org.wordpress.android.fluxc.module.ApplicationPasswordsClientId import org.wordpress.android.fluxc.network.rest.wpapi.Nonce.CookieNonceErrorType.INVALID_CREDENTIALS +import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.ApplicationPasswordsConfiguration import org.wordpress.android.fluxc.store.SiteStore.SiteError import org.wordpress.android.login.LoginAnalyticsListener import org.wordpress.android.util.UrlUtils @@ -55,7 +55,7 @@ class LoginSiteCredentialsViewModel @Inject constructor( private val analyticsTracker: AnalyticsTrackerWrapper, private val appPrefs: AppPrefsWrapper, private val resourceProvider: ResourceProvider, - @ApplicationPasswordsClientId private val applicationPasswordsClientId: String + private val applicationPasswordsConfiguration: ApplicationPasswordsConfiguration ) : ScopedViewModel(savedStateHandle) { companion object { const val SITE_ADDRESS_KEY = "site-address" @@ -82,7 +82,9 @@ class LoginSiteCredentialsViewModel @Inject constructor( private val SiteModel?.fullAuthorizationUrl: String? get() = this?.applicationPasswordsAuthorizeUrl - ?.let { url -> "$url?app_name=$applicationPasswordsClientId&success_url=$REDIRECTION_URL" } + ?.let { url -> + "$url?app_name=${applicationPasswordsConfiguration.applicationName}&success_url=$REDIRECTION_URL" + } val viewState = combine( flowOf(siteAddress.removeSchemeAndSuffix()), diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListItemDataSource.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListItemDataSource.kt index 8d0d7e9c93db..e147e19c019d 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListItemDataSource.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/orders/list/OrderListItemDataSource.kt @@ -1,7 +1,6 @@ package com.woocommerce.android.ui.orders.list import com.woocommerce.android.R -import com.woocommerce.android.config.RemoteConfigRepository import com.woocommerce.android.extensions.getBillingName import com.woocommerce.android.model.TimeGroup import com.woocommerce.android.model.TimeGroup.GROUP_FUTURE @@ -45,8 +44,7 @@ class OrderListItemDataSource @Inject constructor( private val networkStatus: NetworkStatus, private val fetcher: FetchOrdersRepository, private val resourceProvider: ResourceProvider, - private val dateUtils: DateUtils, - private val remoteConfigRepository: RemoteConfigRepository + private val dateUtils: DateUtils ) : ListItemDataSourceInterface { override fun getItemsAndFetchIfNecessary( listDescriptor: WCOrderListDescriptor, @@ -195,8 +193,7 @@ class OrderListItemDataSource @Inject constructor( override fun fetchList(listDescriptor: WCOrderListDescriptor, offset: Long) { val fetchOrderListPayload = FetchOrderListPayload( listDescriptor = listDescriptor, - offset = offset, - useAppPasswordsForJetpackSites = remoteConfigRepository.isJetpackAppPasswordsExperimentEnabled() + offset = offset ) dispatcher.dispatch(WCOrderActionBuilder.newFetchOrderListAction(fetchOrderListPayload)) } diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/prefs/BetaFeaturesFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/prefs/BetaFeaturesFragment.kt index 1f492a5d1c89..dff605f7d515 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/prefs/BetaFeaturesFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/prefs/BetaFeaturesFragment.kt @@ -3,6 +3,7 @@ package com.woocommerce.android.ui.prefs import android.os.Bundle import android.view.View import android.widget.CompoundButton +import androidx.core.view.isVisible import androidx.fragment.app.Fragment import com.google.android.material.snackbar.BaseTransientBottomBar import com.google.android.material.snackbar.Snackbar @@ -11,9 +12,13 @@ import com.woocommerce.android.R import com.woocommerce.android.analytics.AnalyticsEvent.PRODUCT_ADDONS_BETA_FEATURES_SWITCH_TOGGLED import com.woocommerce.android.analytics.AnalyticsTracker import com.woocommerce.android.databinding.FragmentSettingsBetaBinding +import com.woocommerce.android.tools.SelectedSite +import com.woocommerce.android.tools.SiteConnectionType import com.woocommerce.android.ui.prefs.MainSettingsFragment.AppSettingsListener import com.woocommerce.android.util.AnalyticsUtils +import com.woocommerce.android.util.FeatureFlag import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject @AndroidEntryPoint class BetaFeaturesFragment : Fragment(R.layout.fragment_settings_beta) { @@ -21,6 +26,9 @@ class BetaFeaturesFragment : Fragment(R.layout.fragment_settings_beta) { const val TAG = "beta-features" } + @Inject + lateinit var selectedSite: SelectedSite + private val settingsListener by lazy { activity as? AppSettingsListener } @@ -30,6 +38,7 @@ class BetaFeaturesFragment : Fragment(R.layout.fragment_settings_beta) { with(FragmentSettingsBetaBinding.bind(view)) { bindProductAddonsToggle() + bindJetpackAppPasswordsToggle() } } @@ -49,11 +58,22 @@ class BetaFeaturesFragment : Fragment(R.layout.fragment_settings_beta) { } } + private fun FragmentSettingsBetaBinding.bindJetpackAppPasswordsToggle() { + // TODO check the remote feature flag state once available + val isJetpackSite = selectedSite.connectionType != SiteConnectionType.ApplicationPasswords + jetpackAppPasswordsToggle.isVisible = FeatureFlag.APP_PASSWORDS_FOR_JETPACK_SITES.isEnabled() && isJetpackSite + + jetpackAppPasswordsToggle.isChecked = AppPrefs.jetpackAppPasswordsEnabled + jetpackAppPasswordsToggle.setOnCheckedChangeListener { _, isChecked -> + AppPrefs.jetpackAppPasswordsEnabled = isChecked + } + } + override fun onResume() { super.onResume() AnalyticsTracker.trackViewShown(this) - activity?.setTitle(R.string.beta_features) + activity?.setTitle(R.string.experimental_features) } private fun FragmentSettingsBetaBinding.handleToggleChangeFailure(switch: CompoundButton, isChecked: Boolean) { diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/prefs/MainSettingsFragment.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/prefs/MainSettingsFragment.kt index 05e08fd4a14d..4acf4f815c70 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/prefs/MainSettingsFragment.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/ui/prefs/MainSettingsFragment.kt @@ -121,8 +121,6 @@ class MainSettingsFragment : Fragment(R.layout.fragment_settings_main), MainSett AppPrefs.setImageOptimizationEnabled(isChecked) } - updateStoreSettings() - binding.optionHelpAndSupport.setOnClickListener { AnalyticsTracker.track(AnalyticsEvent.MAIN_MENU_CONTACT_SUPPORT_TAPPED) startActivity(HelpActivity.createIntent(requireActivity(), HelpOrigin.SETTINGS, null)) @@ -304,18 +302,6 @@ class MainSettingsFragment : Fragment(R.layout.fragment_settings_main), MainSett binding.optionNotifications.hide() } - private fun updateStoreSettings() { - generateBetaFeaturesTitleList() - .joinToString(", ") - .takeIf { it.isNotEmpty() } - ?.let { binding.optionBetaFeatures.optionValue = it } - } - - private fun generateBetaFeaturesTitleList() = - mutableListOf().apply { - add(getString(R.string.beta_features_add_ons)) - } - private fun showThemeChooser() { val currentTheme = AppPrefs.getAppTheme() val valuesArray = ThemeOption.values().map { getString(it.label) }.toTypedArray() diff --git a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt index a59b54b144c3..ef6a016fbe76 100644 --- a/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt +++ b/WooCommerce/src/main/kotlin/com/woocommerce/android/util/FeatureFlag.kt @@ -16,7 +16,8 @@ enum class FeatureFlag { WOO_POS_HISTORICAL_ORDERS_M1, WOO_POS_LOCAL_CATALOG_M1, HIDE_SITES_FROM_SITE_PICKER, - AI_PRODUCT_IMAGE_BACKGROUND_REMOVAL; + AI_PRODUCT_IMAGE_BACKGROUND_REMOVAL, + APP_PASSWORDS_FOR_JETPACK_SITES; fun isEnabled(context: Context? = null): Boolean { return when (this) { @@ -29,7 +30,8 @@ enum class FeatureFlag { BETTER_CUSTOMER_SEARCH_M2, ORDER_CREATION_AUTO_TAX_RATE, AI_PRODUCT_IMAGE_BACKGROUND_REMOVAL, - WOO_POS_LOCAL_CATALOG_M1 -> PackageUtils.isDebugBuild() + WOO_POS_LOCAL_CATALOG_M1, + APP_PASSWORDS_FOR_JETPACK_SITES -> PackageUtils.isDebugBuild() NEW_SHIPPING_SUPPORT, BULK_UPDATE_ORDERS_STATUS, diff --git a/WooCommerce/src/main/res/layout/fragment_settings_beta.xml b/WooCommerce/src/main/res/layout/fragment_settings_beta.xml index 02d1a53b01f4..fddd26ee91d0 100644 --- a/WooCommerce/src/main/res/layout/fragment_settings_beta.xml +++ b/WooCommerce/src/main/res/layout/fragment_settings_beta.xml @@ -14,6 +14,13 @@ app:toggleOptionDesc="@string/settings_enable_product_addons_teaser_message" app:toggleOptionTitle="@string/settings_enable_product_addons_teaser_title" /> + + diff --git a/WooCommerce/src/main/res/layout/fragment_settings_main.xml b/WooCommerce/src/main/res/layout/fragment_settings_main.xml index de2bc954b234..0c341c4dd1d4 100644 --- a/WooCommerce/src/main/res/layout/fragment_settings_main.xml +++ b/WooCommerce/src/main/res/layout/fragment_settings_main.xml @@ -203,8 +203,7 @@ android:id="@+id/option_beta_features" android:layout_width="match_parent" android:layout_height="wrap_content" - app:optionTitle="@string/beta_features" - app:optionValue="@string/beta_features_add_ons" /> + app:optionTitle="@string/experimental_features" />