Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9789e81
Refactor handling of Application Passwords configuration
hichamboushaba Aug 30, 2025
6161d74
Rename property
hichamboushaba Aug 30, 2025
cb1cf6e
Add new property to control whether feature is enabled or not for WPC…
hichamboushaba Aug 30, 2025
225395d
Copy `WooExperimentalNetwork` logic to `WooNetwork`
hichamboushaba Aug 30, 2025
856bbe1
Remove WooExperimentalNetwork
hichamboushaba Aug 30, 2025
3444de8
Force using Jetpack tunnel when feature is disabled
hichamboushaba Aug 30, 2025
aeb3d6b
Remove unused argument
hichamboushaba Aug 30, 2025
7df9071
Remove unused remote config key
hichamboushaba Aug 30, 2025
65d2f19
Remove old code related to the initial experiment
hichamboushaba Aug 30, 2025
edc6d45
Improve log message for failed requests
hichamboushaba Aug 30, 2025
90d18fb
Add a feature flag for the feature
hichamboushaba Aug 30, 2025
a442412
Minor refactoring for the configuration class
hichamboushaba Sep 1, 2025
60312f7
Make `isEnabledForDirectAccess` enabled by default
hichamboushaba Sep 1, 2025
043d3dc
Update unit tests
hichamboushaba Sep 1, 2025
de41eb5
Fix detekt issues
hichamboushaba Sep 1, 2025
c7f9c85
Fix wear app build issue
hichamboushaba Sep 1, 2025
8ea3d7f
Fix issue for Jetpack CP sites
hichamboushaba Sep 2, 2025
bd77057
Add preference key for the experimental toggle
hichamboushaba Sep 3, 2025
44044c9
Add a preference toggle to the experimental features screen
hichamboushaba Sep 3, 2025
2ef0b44
Rename settings entry to "Experimental features"
hichamboushaba Sep 3, 2025
1700937
Merge pull request #14559 from woocommerce/issue/WOOMOB-1183-jp-app-p…
irfano Sep 5, 2025
8ee5125
Remove outdated comment section
hichamboushaba Sep 5, 2025
3e845a0
Merge branch 'trunk' into issue/WOOMOB-1126-app-passwords-network
hichamboushaba Sep 5, 2025
586a357
Remove old way for checking for app passwords support
hichamboushaba Sep 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +22 to +23
Copy link
Member Author

@hichamboushaba hichamboushaba Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The wear app didn't support Application Passwords before #12124, and we are keeping the same behavior.

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

/**
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand All @@ -37,11 +35,7 @@ class FirebaseRemoteConfigRepository @Inject constructor(
private val _fetchStatus = MutableStateFlow(RemoteConfigFetchStatus.Pending)
override val fetchStatus: Flow<RemoteConfigFetchStatus> = _fetchStatus.asStateFlow()

private val defaultValues by lazy {
mapOf(
KEY_ENABLE_JETPACK_APP_PASSWORDS_EXPERIMENT to false.toString()
)
}
private val defaultValues = emptyMap<String, String>()

init {
remoteConfig.apply {
Expand Down Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import kotlinx.coroutines.flow.Flow
interface RemoteConfigRepository {
val fetchStatus: Flow<RemoteConfigFetchStatus>
fun fetchRemoteConfig()

fun isJetpackAppPasswordsExperimentEnabled(): Boolean
}

enum class RemoteConfigFetchStatus {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand All @@ -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()),
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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<WCOrderListDescriptor, OrderListItemIdentifier, OrderListItemUIType> {
override fun getItemsAndFetchIfNecessary(
listDescriptor: WCOrderListDescriptor,
Expand Down Expand Up @@ -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))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -11,16 +12,23 @@ 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) {
companion object {
const val TAG = "beta-features"
}

@Inject
lateinit var selectedSite: SelectedSite

private val settingsListener by lazy {
activity as? AppSettingsListener
}
Expand All @@ -30,6 +38,7 @@ class BetaFeaturesFragment : Fragment(R.layout.fragment_settings_beta) {

with(FragmentSettingsBetaBinding.bind(view)) {
bindProductAddonsToggle()
bindJetpackAppPasswordsToggle()
}
}

Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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<String>().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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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,
Expand Down
7 changes: 7 additions & 0 deletions WooCommerce/src/main/res/layout/fragment_settings_beta.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
app:toggleOptionDesc="@string/settings_enable_product_addons_teaser_message"
app:toggleOptionTitle="@string/settings_enable_product_addons_teaser_title" />

<com.woocommerce.android.ui.prefs.WCSettingsToggleOptionView
android:id="@+id/jetpackAppPasswordsToggle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:toggleOptionDesc="@string/settings_enable_jetpack_app_passwords_message"
app:toggleOptionTitle="@string/settings_enable_jetpack_app_passwords_title" />

<View style="@style/Woo.Divider" />

</LinearLayout>
3 changes: 1 addition & 2 deletions WooCommerce/src/main/res/layout/fragment_settings_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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" />

<!--
Send Feedback
Expand Down
6 changes: 3 additions & 3 deletions WooCommerce/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2505,9 +2505,7 @@
<string name="settings">Settings</string>
<string name="privacy_settings">Privacy settings</string>
<string name="send_feedback">Send feedback</string>
<string name="beta_features">Beta features</string>
<string name="beta_features_add_ons">View Add-ons</string>
<string name="beta_features_simple_payments">Simple payments</string>
<string name="experimental_features">Experimental features</string>
<string name="settings_signout">Log out</string>
<string name="settings_close_account">Close Account</string>
<string name="settings_close_account_dialog_title">Confirm Close Account</string>
Expand Down Expand Up @@ -2612,6 +2610,8 @@
<string name="plugin_state_inactive">Inactive</string>
<string name="plugin_state_auto_managed">Auto-managed</string>
<string name="plugins_error_message">An error occurred while loading installed plugins</string>
<string name="settings_enable_jetpack_app_passwords_title">Application Passwords</string>
<string name="settings_enable_jetpack_app_passwords_message">Enable application passwords to let the app directly fetch data from your WooCommerce site instead of doing so through Jetpack connection.</string>

<!--
Developer Options Screen
Expand Down
Loading