From edb4b595d76a58b3cc7d0c5cb60370482cbc6f35 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 10 Nov 2025 10:59:48 +0100 Subject: [PATCH 01/23] Updating RS library --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 72b6a4c3ab3c..d3ef036362ae 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -101,7 +101,7 @@ wellsql = '2.0.0' wordpress-aztec = 'v2.1.4' wordpress-lint = '2.2.0' wordpress-persistent-edittext = '1.0.2' -wordpress-rs = 'trunk-fb107b497caaf2b1f4ffcf9f487784792561a645' +wordpress-rs = 'trunk-7d9af2c9f5ae78d43c77248adcba4af5798e31b6' wordpress-utils = '3.14.0' automattic-ucrop = '2.2.11' zendesk = '5.5.1' From 971444dcb85fd6c89125fe316aa5fd0e7cafb74a Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 10 Nov 2025 11:00:12 +0100 Subject: [PATCH 02/23] Providing the client with the new authentication method --- .../rest/wpapi/rs/WpApiClientProvider.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt index 8fe5542ff4be..2acc5da142d0 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt @@ -4,6 +4,10 @@ import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.WpAppNotifierHandler import rs.wordpress.api.kotlin.WpApiClient import rs.wordpress.api.kotlin.WpRequestExecutor +import uniffi.wp_api.AutoDiscoveryAttemptSuccess +import uniffi.wp_api.CookiesNonceAuthenticationProvider +import uniffi.wp_api.ParsedUrl +import uniffi.wp_api.WpApiDetails import uniffi.wp_api.WpAppNotifier import uniffi.wp_api.WpAuthenticationProvider import java.net.URL @@ -33,5 +37,36 @@ class WpApiClientProvider @Inject constructor( return client } + fun getWpApiClientCookiesNonceAuthentication( + site: SiteModel, + applicationPasswordsAuthenticationUrl: String, + ): WpApiClient { + val parsedSiteUrl = ParsedUrl.parse(site.url) + val apiRootUrl = ParsedUrl.parse(site.buildUrl()) + val requestExecutor = WpRequestExecutor() + val authProvider = CookiesNonceAuthenticationProvider( + username = "", + password = "", + detail = AutoDiscoveryAttemptSuccess( + parsedSiteUrl = parsedSiteUrl, + apiRootUrl = apiRootUrl, + apiDetails = WpApiDetails(site.url), + applicationPasswordsAuthenticationUrl = ParsedUrl.parse(applicationPasswordsAuthenticationUrl) + ), + requestExecutor = requestExecutor + ) + val client = WpApiClient( + wpOrgSiteApiRootUrl = URL(site.buildUrl()), + authProvider = authProvider, + requestExecutor = requestExecutor, + appNotifier = object : WpAppNotifier { + override suspend fun requestedWithInvalidAuthentication(requestUrl: String) { + wpAppNotifierHandler.notifyRequestedWithInvalidAuthentication(site) + } + } + ) + return client + } + private fun SiteModel.buildUrl(): String = wpApiRestUrl ?: "${url}/wp-json" } From f74bcc65677dea0d8a7db0bfcc465002e3ec9c92 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 10 Nov 2025 11:00:26 +0100 Subject: [PATCH 03/23] Handling password creation --- .../ApplicationPasswordViewModelSlice.kt | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index dec0bf78fb79..7b10634c0785 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.wordpress.android.R import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper import org.wordpress.android.ui.mysite.MySiteCardAndItem @@ -16,11 +17,15 @@ import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures.Feature import org.wordpress.android.ui.utils.ListItemInteraction import org.wordpress.android.ui.utils.UiString +import org.wordpress.android.util.AppLog import org.wordpress.android.viewmodel.Event +import rs.wordpress.api.kotlin.WpRequestResult +import uniffi.wp_api.ApplicationPasswordCreateParams import javax.inject.Inject class ApplicationPasswordViewModelSlice @Inject constructor( private val applicationPasswordLoginHelper: ApplicationPasswordLoginHelper, + private val wpApiClientProvider: WpApiClientProvider, private val siteStore: SiteStore, private val experimentalFeatures: ExperimentalFeatures, ) { @@ -85,10 +90,24 @@ class ApplicationPasswordViewModelSlice @Inject constructor( private fun onClick(authorizationUrlComplete: String) { - _onNavigation.postValue( - Event( - SiteNavigationAction.OpenApplicationPasswordAuthentication(authorizationUrlComplete) + val client = wpApiClientProvider.getWpApiClientCookiesNonceAuthentication() + val userIdResponse = client.request { requestBuilder -> + requestBuilder.applicationPasswords().create( + userId = "", + params = ApplicationPasswordCreateParams( + appId = "", + name = "" + ) ) - ) + } + when (userIdResponse) { + is WpRequestResult.Success -> { + userIdResponse.response.data + } + + else -> { + val error = "Error getting current user Id" + } + } } } From 6ad7752320660a86f23f0b4cd04b0fe8af81e413 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 10 Nov 2025 11:20:24 +0100 Subject: [PATCH 04/23] Handling authentication --- .../login/ApplicationPasswordLoginHelper.kt | 4 +- .../ApplicationPasswordViewModelSlice.kt | 59 +++++++++++++------ 2 files changed, 43 insertions(+), 20 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt index 9619b459f977..c34c6c188fe5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt @@ -204,8 +204,8 @@ class ApplicationPasswordLoginHelper @Inject constructor( } companion object { - private const val ANDROID_JETPACK_CLIENT = "android-jetpack-client" - private const val ANDROID_WORDPRESS_CLIENT = "android-wordpress-client" + const val ANDROID_JETPACK_CLIENT = "android-jetpack-client" + const val ANDROID_WORDPRESS_CLIENT = "android-wordpress-client" private const val JETPACK_SUCCESS_URL = "jetpack://app-pass-authorize" private const val WORDPRESS_SUCCESS_URL = "wordpress://app-pass-authorize" } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index 7b10634c0785..8506fc5bb189 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -9,6 +9,10 @@ import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_JETPACK_CLIENT +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_WORDPRESS_CLIENT +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.JETPACK_SUCCESS_URL +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.WORDPRESS_SUCCESS_URL import org.wordpress.android.ui.mysite.MySiteCardAndItem import org.wordpress.android.ui.mysite.MySiteCardAndItem.Card.QuickLinksItem.QuickLinkItem import org.wordpress.android.ui.mysite.SiteNavigationAction @@ -18,6 +22,7 @@ import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures. import org.wordpress.android.ui.utils.ListItemInteraction import org.wordpress.android.ui.utils.UiString import org.wordpress.android.util.AppLog +import org.wordpress.android.util.BuildConfigWrapper import org.wordpress.android.viewmodel.Event import rs.wordpress.api.kotlin.WpRequestResult import uniffi.wp_api.ApplicationPasswordCreateParams @@ -28,6 +33,7 @@ class ApplicationPasswordViewModelSlice @Inject constructor( private val wpApiClientProvider: WpApiClientProvider, private val siteStore: SiteStore, private val experimentalFeatures: ExperimentalFeatures, + private val buildConfigWrapper: BuildConfigWrapper, ) { lateinit var scope: CoroutineScope @@ -69,19 +75,22 @@ class ApplicationPasswordViewModelSlice @Inject constructor( if (authorizationUrlComplete.isEmpty()) { uiModelMutable.postValue(null) } else { - postAuthenticationUrl(authorizationUrlComplete) + postAuthenticationUrl(site, authorizationUrlComplete) } } } - private fun postAuthenticationUrl(authorizationUrlComplete: String) { + private fun postAuthenticationUrl( + site: SiteModel, + authorizationUrlComplete: String + ) { uiModelMutable.postValue( MySiteCardAndItem.Card.QuickLinksItem( listOf( QuickLinkItem( label = UiString.UiStringRes(R.string.application_password_title), icon = R.drawable.ic_lock_white_24dp, - onClick = ListItemInteraction.create { onClick(authorizationUrlComplete) } + onClick = ListItemInteraction.create { onClick(site, authorizationUrlComplete) } ) ) ) @@ -89,24 +98,38 @@ class ApplicationPasswordViewModelSlice @Inject constructor( } - private fun onClick(authorizationUrlComplete: String) { - val client = wpApiClientProvider.getWpApiClientCookiesNonceAuthentication() - val userIdResponse = client.request { requestBuilder -> - requestBuilder.applicationPasswords().create( - userId = "", - params = ApplicationPasswordCreateParams( - appId = "", - name = "" - ) + private fun onClick( + site: SiteModel, + authorizationUrlComplete: String + ) { + scope.launch { + val client = wpApiClientProvider.getWpApiClientCookiesNonceAuthentication( + site = site, + applicationPasswordsAuthenticationUrl = authorizationUrlComplete, ) - } - when (userIdResponse) { - is WpRequestResult.Success -> { - userIdResponse.response.data + val appName = if (buildConfigWrapper.isJetpackApp) { + ANDROID_JETPACK_CLIENT + } else { + ANDROID_WORDPRESS_CLIENT + } + val userIdResponse = client.request { requestBuilder -> + requestBuilder.applicationPasswords().create( + userId = "", + params = ApplicationPasswordCreateParams( + appId = appName, + name = "$appName-${System.currentTimeMillis()}" + ) + ) } + when (userIdResponse) { + is WpRequestResult.Success -> { + userIdResponse.response.data + // TODO: store credentials + } - else -> { - val error = "Error getting current user Id" + else -> { + // TODO: log error + } } } } From 52ffc2dc0c93eef91a028b11edf56a5f562dc15d Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 10 Nov 2025 13:40:54 +0100 Subject: [PATCH 05/23] Minor fix --- .../fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt index 2acc5da142d0..0c508c5ea130 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt @@ -44,10 +44,10 @@ class WpApiClientProvider @Inject constructor( val parsedSiteUrl = ParsedUrl.parse(site.url) val apiRootUrl = ParsedUrl.parse(site.buildUrl()) val requestExecutor = WpRequestExecutor() - val authProvider = CookiesNonceAuthenticationProvider( - username = "", - password = "", - detail = AutoDiscoveryAttemptSuccess( + val cookiesNonceProvider = CookiesNonceAuthenticationProvider( + username = site.apiRestUsernamePlain, + password = site.apiRestPasswordPlain, + details = AutoDiscoveryAttemptSuccess( parsedSiteUrl = parsedSiteUrl, apiRootUrl = apiRootUrl, apiDetails = WpApiDetails(site.url), @@ -57,7 +57,7 @@ class WpApiClientProvider @Inject constructor( ) val client = WpApiClient( wpOrgSiteApiRootUrl = URL(site.buildUrl()), - authProvider = authProvider, + authProvider = cookiesNonceProvider, requestExecutor = requestExecutor, appNotifier = object : WpAppNotifier { override suspend fun requestedWithInvalidAuthentication(requestUrl: String) { From ec0ac066bb5b9e7efc427e7ee1bf7aa2240c66d3 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 10 Nov 2025 16:16:59 +0100 Subject: [PATCH 06/23] Error handling --- .../ApplicationPasswordViewModelSlice.kt | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index 8506fc5bb189..66c21ebc6853 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -8,11 +8,11 @@ import org.wordpress.android.R import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider import org.wordpress.android.fluxc.store.SiteStore +import org.wordpress.android.fluxc.utils.AppLogWrapper import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_JETPACK_CLIENT import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_WORDPRESS_CLIENT -import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.JETPACK_SUCCESS_URL -import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.WORDPRESS_SUCCESS_URL +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.UriLogin import org.wordpress.android.ui.mysite.MySiteCardAndItem import org.wordpress.android.ui.mysite.MySiteCardAndItem.Card.QuickLinksItem.QuickLinkItem import org.wordpress.android.ui.mysite.SiteNavigationAction @@ -21,12 +21,15 @@ import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures.Feature import org.wordpress.android.ui.utils.ListItemInteraction import org.wordpress.android.ui.utils.UiString +import org.wordpress.android.ui.utils.UiString.UiStringRes import org.wordpress.android.util.AppLog import org.wordpress.android.util.BuildConfigWrapper +import org.wordpress.android.util.getEmailValidationMessage import org.wordpress.android.viewmodel.Event import rs.wordpress.api.kotlin.WpRequestResult import uniffi.wp_api.ApplicationPasswordCreateParams import javax.inject.Inject +import kotlin.String class ApplicationPasswordViewModelSlice @Inject constructor( private val applicationPasswordLoginHelper: ApplicationPasswordLoginHelper, @@ -34,6 +37,7 @@ class ApplicationPasswordViewModelSlice @Inject constructor( private val siteStore: SiteStore, private val experimentalFeatures: ExperimentalFeatures, private val buildConfigWrapper: BuildConfigWrapper, + private val appLogWrapper: AppLogWrapper, ) { lateinit var scope: CoroutineScope @@ -123,12 +127,39 @@ class ApplicationPasswordViewModelSlice @Inject constructor( } when (userIdResponse) { is WpRequestResult.Success -> { - userIdResponse.response.data - // TODO: store credentials + val name = userIdResponse.response.data.name + val password = userIdResponse.response.data.password + applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom( + UriLogin( + siteUrl = site.url, + user = name, + password = password, + apiRootUrl = + ) + ) + _onSnackbarMessage.postValue( + Event( + SnackbarMessageHolder( + UiString.UiStringResWithParams(R.string.application_password_credentials_stored, + UiString.UiStringText(site.url) + ) + ) + ) + ) } else -> { - // TODO: log error + appLogWrapper.e(AppLog.T.API, "Error creating application password") + _onSnackbarMessage.postValue( + Event( + SnackbarMessageHolder( + UiString.UiStringResWithParams( + R.string.application_password_credentials_storing_error, + UiString.UiStringText(site.url) + ) + ) + ) + ) } } } From 8f5698f8d75c6ec1a2679b35cff112ea22a27647 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 10 Nov 2025 16:20:01 +0100 Subject: [PATCH 07/23] Refactor to get site info --- .../applicationpassword/ApplicationPasswordViewModelSlice.kt | 3 ++- .../android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index 66c21ebc6853..03c90f2a76bf 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -129,12 +129,13 @@ class ApplicationPasswordViewModelSlice @Inject constructor( is WpRequestResult.Success -> { val name = userIdResponse.response.data.name val password = userIdResponse.response.data.password + val apiRootUrl = wpApiClientProvider.getApiRootUrlFrom(site) applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom( UriLogin( siteUrl = site.url, user = name, password = password, - apiRootUrl = + apiRootUrl = apiRootUrl ) ) _onSnackbarMessage.postValue( diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt index 0c508c5ea130..df7d30513d8e 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt @@ -68,5 +68,7 @@ class WpApiClientProvider @Inject constructor( return client } + fun getApiRootUrlFrom(site: SiteModel): String = site.buildUrl() + private fun SiteModel.buildUrl(): String = wpApiRestUrl ?: "${url}/wp-json" } From 8f9129cb6cf0c7e02d0f285e187f6c7d9853a189 Mon Sep 17 00:00:00 2001 From: adalpari Date: Wed, 12 Nov 2025 11:39:51 +0100 Subject: [PATCH 08/23] updating rs version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d3ef036362ae..6e9262972ab0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -101,7 +101,7 @@ wellsql = '2.0.0' wordpress-aztec = 'v2.1.4' wordpress-lint = '2.2.0' wordpress-persistent-edittext = '1.0.2' -wordpress-rs = 'trunk-7d9af2c9f5ae78d43c77248adcba4af5798e31b6' +wordpress-rs = 'trunk-3d2dafdc1f8b058b4ed9101673fdf690671da73c' wordpress-utils = '3.14.0' automattic-ucrop = '2.2.11' zendesk = '5.5.1' From 638d80200dac0dc8bde2519b61ab5d39010e8a11 Mon Sep 17 00:00:00 2001 From: adalpari Date: Wed, 12 Nov 2025 11:40:12 +0100 Subject: [PATCH 09/23] Calling createForCurrentUser --- .../applicationpassword/ApplicationPasswordViewModelSlice.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index 03c90f2a76bf..b562be8bc899 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -117,8 +117,7 @@ class ApplicationPasswordViewModelSlice @Inject constructor( ANDROID_WORDPRESS_CLIENT } val userIdResponse = client.request { requestBuilder -> - requestBuilder.applicationPasswords().create( - userId = "", + requestBuilder.applicationPasswords().createForCurrentUser( params = ApplicationPasswordCreateParams( appId = appName, name = "$appName-${System.currentTimeMillis()}" From 88dea9c9911bc1153a5549cddf5544809f2e15f1 Mon Sep 17 00:00:00 2001 From: adalpari Date: Wed, 12 Nov 2025 11:57:23 +0100 Subject: [PATCH 10/23] Fixing AIBot api changes --- .../aibot/repository/AIBotSupportRepository.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/support/aibot/repository/AIBotSupportRepository.kt b/WordPress/src/main/java/org/wordpress/android/support/aibot/repository/AIBotSupportRepository.kt index 86b37527a7ac..005fffdbcf2b 100644 --- a/WordPress/src/main/java/org/wordpress/android/support/aibot/repository/AIBotSupportRepository.kt +++ b/WordPress/src/main/java/org/wordpress/android/support/aibot/repository/AIBotSupportRepository.kt @@ -15,6 +15,8 @@ import uniffi.wp_api.AddMessageToBotConversationParams import uniffi.wp_api.BotConversationSummary import uniffi.wp_api.CreateBotConversationParams import uniffi.wp_api.GetBotConversationParams +import uniffi.wp_api.ListBotConversationsParams +import uniffi.wp_api.ListBotConversationsSummaryMethod import java.util.Date import javax.inject.Inject import javax.inject.Named @@ -54,7 +56,12 @@ class AIBotSupportRepository @Inject constructor( suspend fun loadConversations(): List = withContext(ioDispatcher) { val response = wpComApiClient.request { requestBuilder -> - requestBuilder.supportBots().getBotConverationList(BOT_ID) + requestBuilder.supportBots().getBotConversationList( + botId = BOT_ID, + params = ListBotConversationsParams( + summaryMethod = ListBotConversationsSummaryMethod.LAST_MESSAGE + ) + ) } when (response) { is WpRequestResult.Success -> { @@ -154,8 +161,8 @@ class AIBotSupportRepository @Inject constructor( BotConversation ( id = chatId.toLong(), createdAt = createdAt, - mostRecentMessageDate = lastMessage.createdAt, - lastMessage = lastMessage.content, + mostRecentMessageDate = summaryMessage.createdAt, + lastMessage = summaryMessage.content, messages = listOf() ) From f14246abcb54a58e2678ff3c9f7c1ea1c756ee1b Mon Sep 17 00:00:00 2001 From: adalpari Date: Wed, 12 Nov 2025 11:57:36 +0100 Subject: [PATCH 11/23] Making a real call --- .../ApplicationPasswordViewModelSlice.kt | 15 +++-------- .../rest/wpapi/rs/WpApiClientProvider.kt | 27 +++++++------------ 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index b562be8bc899..e07e69203c3e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -79,22 +79,19 @@ class ApplicationPasswordViewModelSlice @Inject constructor( if (authorizationUrlComplete.isEmpty()) { uiModelMutable.postValue(null) } else { - postAuthenticationUrl(site, authorizationUrlComplete) + postAuthenticationUrl(site) } } } - private fun postAuthenticationUrl( - site: SiteModel, - authorizationUrlComplete: String - ) { + private fun postAuthenticationUrl(site: SiteModel) { uiModelMutable.postValue( MySiteCardAndItem.Card.QuickLinksItem( listOf( QuickLinkItem( label = UiString.UiStringRes(R.string.application_password_title), icon = R.drawable.ic_lock_white_24dp, - onClick = ListItemInteraction.create { onClick(site, authorizationUrlComplete) } + onClick = ListItemInteraction.create { onClick(site) } ) ) ) @@ -102,14 +99,10 @@ class ApplicationPasswordViewModelSlice @Inject constructor( } - private fun onClick( - site: SiteModel, - authorizationUrlComplete: String - ) { + private fun onClick(site: SiteModel) { scope.launch { val client = wpApiClientProvider.getWpApiClientCookiesNonceAuthentication( site = site, - applicationPasswordsAuthenticationUrl = authorizationUrlComplete, ) val appName = if (buildConfigWrapper.isJetpackApp) { ANDROID_JETPACK_CLIENT diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt index df7d30513d8e..e2807502ba0c 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt @@ -37,27 +37,20 @@ class WpApiClientProvider @Inject constructor( return client } - fun getWpApiClientCookiesNonceAuthentication( - site: SiteModel, - applicationPasswordsAuthenticationUrl: String, - ): WpApiClient { - val parsedSiteUrl = ParsedUrl.parse(site.url) - val apiRootUrl = ParsedUrl.parse(site.buildUrl()) + fun getWpApiClientCookiesNonceAuthentication(site: SiteModel): WpApiClient { + val apiRoot = site.buildUrl() + val apiRootUrl = URL(apiRoot) val requestExecutor = WpRequestExecutor() - val cookiesNonceProvider = CookiesNonceAuthenticationProvider( - username = site.apiRestUsernamePlain, - password = site.apiRestPasswordPlain, - details = AutoDiscoveryAttemptSuccess( - parsedSiteUrl = parsedSiteUrl, - apiRootUrl = apiRootUrl, - apiDetails = WpApiDetails(site.url), - applicationPasswordsAuthenticationUrl = ParsedUrl.parse(applicationPasswordsAuthenticationUrl) - ), + val cookiesNonceProvider = CookiesNonceAuthenticationProvider.withSiteUrl( + url = apiRoot, + username = site.username, + password = site.password, requestExecutor = requestExecutor ) + val authProvider = WpAuthenticationProvider.dynamic(cookiesNonceProvider) val client = WpApiClient( - wpOrgSiteApiRootUrl = URL(site.buildUrl()), - authProvider = cookiesNonceProvider, + wpOrgSiteApiRootUrl = apiRootUrl, + authProvider = authProvider, requestExecutor = requestExecutor, appNotifier = object : WpAppNotifier { override suspend fun requestedWithInvalidAuthentication(requestUrl: String) { From 909a486aeede11da5400ca2fcab91525d69410d4 Mon Sep 17 00:00:00 2001 From: adalpari Date: Wed, 12 Nov 2025 12:48:11 +0100 Subject: [PATCH 12/23] Using null appId --- .../applicationpassword/ApplicationPasswordViewModelSlice.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index e07e69203c3e..fcc757b7f74a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.launch import org.wordpress.android.R import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider +import org.wordpress.android.fluxc.network.rest.wpcom.auth.AppSecrets import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.fluxc.utils.AppLogWrapper import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper @@ -38,6 +39,7 @@ class ApplicationPasswordViewModelSlice @Inject constructor( private val experimentalFeatures: ExperimentalFeatures, private val buildConfigWrapper: BuildConfigWrapper, private val appLogWrapper: AppLogWrapper, + private val appSecrets: AppSecrets ) { lateinit var scope: CoroutineScope @@ -112,7 +114,7 @@ class ApplicationPasswordViewModelSlice @Inject constructor( val userIdResponse = client.request { requestBuilder -> requestBuilder.applicationPasswords().createForCurrentUser( params = ApplicationPasswordCreateParams( - appId = appName, + appId = null, name = "$appName-${System.currentTimeMillis()}" ) ) From b336cfdb6ce7047d5873894177cd5da9e8188561 Mon Sep 17 00:00:00 2001 From: adalpari Date: Thu, 13 Nov 2025 13:40:35 +0100 Subject: [PATCH 13/23] Minor apiRootUrl fix --- .../android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt index e2807502ba0c..124b373ace8c 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt @@ -63,5 +63,6 @@ class WpApiClientProvider @Inject constructor( fun getApiRootUrlFrom(site: SiteModel): String = site.buildUrl() - private fun SiteModel.buildUrl(): String = wpApiRestUrl ?: "${url}/wp-json" + private fun SiteModel.buildUrl(): String = + wpApiRestUrl?.takeIf { it.isNotEmpty() } ?: "${url}/wp-json" } From 143e3be4607d40befeeb83d2288c122e98d83771 Mon Sep 17 00:00:00 2001 From: adalpari Date: Thu, 13 Nov 2025 13:53:14 +0100 Subject: [PATCH 14/23] Using WPuuid --- .../ApplicationPasswordViewModelSlice.kt | 13 +++++++------ .../network/rest/wpapi/rs/WpApiClientProvider.kt | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index fcc757b7f74a..4223b6b690ee 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -29,6 +29,7 @@ import org.wordpress.android.util.getEmailValidationMessage import org.wordpress.android.viewmodel.Event import rs.wordpress.api.kotlin.WpRequestResult import uniffi.wp_api.ApplicationPasswordCreateParams +import uniffi.wp_api.WpUuid import javax.inject.Inject import kotlin.String @@ -39,7 +40,6 @@ class ApplicationPasswordViewModelSlice @Inject constructor( private val experimentalFeatures: ExperimentalFeatures, private val buildConfigWrapper: BuildConfigWrapper, private val appLogWrapper: AppLogWrapper, - private val appSecrets: AppSecrets ) { lateinit var scope: CoroutineScope @@ -111,18 +111,19 @@ class ApplicationPasswordViewModelSlice @Inject constructor( } else { ANDROID_WORDPRESS_CLIENT } - val userIdResponse = client.request { requestBuilder -> + val appId = WpUuid() + val response = client.request { requestBuilder -> requestBuilder.applicationPasswords().createForCurrentUser( params = ApplicationPasswordCreateParams( - appId = null, + appId = appId.uuidString(), name = "$appName-${System.currentTimeMillis()}" ) ) } - when (userIdResponse) { + when (response) { is WpRequestResult.Success -> { - val name = userIdResponse.response.data.name - val password = userIdResponse.response.data.password + val name = response.response.data.name + val password = response.response.data.password val apiRootUrl = wpApiClientProvider.getApiRootUrlFrom(site) applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom( UriLogin( diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt index 124b373ace8c..bd49203dc61b 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt @@ -42,7 +42,7 @@ class WpApiClientProvider @Inject constructor( val apiRootUrl = URL(apiRoot) val requestExecutor = WpRequestExecutor() val cookiesNonceProvider = CookiesNonceAuthenticationProvider.withSiteUrl( - url = apiRoot, + url = site.url, username = site.username, password = site.password, requestExecutor = requestExecutor From fd176a82a3edf87b0078daab5c31f4a5ca426aec Mon Sep 17 00:00:00 2001 From: adalpari Date: Thu, 13 Nov 2025 14:23:31 +0100 Subject: [PATCH 15/23] Minor refactor --- .../fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt index bd49203dc61b..0e3400494eb3 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt @@ -38,8 +38,6 @@ class WpApiClientProvider @Inject constructor( } fun getWpApiClientCookiesNonceAuthentication(site: SiteModel): WpApiClient { - val apiRoot = site.buildUrl() - val apiRootUrl = URL(apiRoot) val requestExecutor = WpRequestExecutor() val cookiesNonceProvider = CookiesNonceAuthenticationProvider.withSiteUrl( url = site.url, @@ -49,7 +47,7 @@ class WpApiClientProvider @Inject constructor( ) val authProvider = WpAuthenticationProvider.dynamic(cookiesNonceProvider) val client = WpApiClient( - wpOrgSiteApiRootUrl = apiRootUrl, + wpOrgSiteApiRootUrl = URL(site.buildUrl()), authProvider = authProvider, requestExecutor = requestExecutor, appNotifier = object : WpAppNotifier { From 3b3bb2799b856110521de9831590cf93c6b0c308 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 17 Nov 2025 11:20:37 +0100 Subject: [PATCH 16/23] Using custom okhttp client with cookie jat --- .../ApplicationPasswordViewModelSlice.kt | 2 +- .../rest/wpapi/rs/WpApiClientProvider.kt | 27 ++++++++++++++++--- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index 4223b6b690ee..8bd557eec486 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -122,7 +122,7 @@ class ApplicationPasswordViewModelSlice @Inject constructor( } when (response) { is WpRequestResult.Success -> { - val name = response.response.data.name + val name = site.username // This should be the response name, but it's retuning a wrong value val password = response.response.data.password val apiRootUrl = wpApiClientProvider.getApiRootUrlFrom(site) applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom( diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt index 0e3400494eb3..213c37f7246f 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt @@ -1,13 +1,15 @@ package org.wordpress.android.fluxc.network.rest.wpapi.rs +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl +import okhttp3.OkHttpClient import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.network.rest.wpapi.applicationpasswords.WpAppNotifierHandler import rs.wordpress.api.kotlin.WpApiClient +import rs.wordpress.api.kotlin.WpHttpClient import rs.wordpress.api.kotlin.WpRequestExecutor -import uniffi.wp_api.AutoDiscoveryAttemptSuccess import uniffi.wp_api.CookiesNonceAuthenticationProvider -import uniffi.wp_api.ParsedUrl -import uniffi.wp_api.WpApiDetails import uniffi.wp_api.WpAppNotifier import uniffi.wp_api.WpAuthenticationProvider import java.net.URL @@ -38,7 +40,24 @@ class WpApiClientProvider @Inject constructor( } fun getWpApiClientCookiesNonceAuthentication(site: SiteModel): WpApiClient { - val requestExecutor = WpRequestExecutor() + // Create OkHttpClient with cookie jar for cookies/nonce authentication + val okHttpClient = OkHttpClient.Builder() + .cookieJar(object : CookieJar { + private val cookieStore = mutableMapOf>() + + override fun saveFromResponse(url: HttpUrl, cookies: List) { + cookieStore[url.host] = cookies + } + + override fun loadForRequest(url: HttpUrl): List { + return cookieStore[url.host] ?: emptyList() + } + }) + .build() + + val httpClient = WpHttpClient.CustomOkHttpClient(okHttpClient) + val requestExecutor = WpRequestExecutor(httpClient) + val cookiesNonceProvider = CookiesNonceAuthenticationProvider.withSiteUrl( url = site.url, username = site.username, From 170fda7d9338a83c042c740ec1e8940504540b3c Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 17 Nov 2025 12:36:52 +0100 Subject: [PATCH 17/23] Extracting into SiteFragment --- .../android/ui/mysite/MySiteFragment.kt | 3 + .../android/ui/mysite/MySiteViewModel.kt | 77 +++++++++++++++++++ .../android/ui/mysite/SiteNavigationAction.kt | 1 + .../ApplicationPasswordViewModelSlice.kt | 72 ++--------------- 4 files changed, 88 insertions(+), 65 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt index 091f121e0978..76b80476ad3f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt @@ -741,6 +741,9 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment), is SiteNavigationAction.OpenApplicationPasswordAuthentication -> { activityNavigator.openApplicationPasswordLogin(requireActivity(), action.url) } + is SiteNavigationAction.OpenApplicationPasswordAutoAuthentication -> { + viewModel.createApplicationPassword(action.site) + } } private fun openBloganuaryNudgeOverlay(isPromptsEnabled: Boolean) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt index 833c166cb6dd..0ee7f475f396 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt @@ -17,11 +17,17 @@ import org.wordpress.android.R import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.fluxc.Dispatcher import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider import org.wordpress.android.fluxc.store.AccountStore import org.wordpress.android.fluxc.store.PostStore.OnPostUploaded import org.wordpress.android.fluxc.store.QuickStartStore.QuickStartTask +import org.wordpress.android.fluxc.utils.AppLogWrapper import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.modules.UI_THREAD +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_JETPACK_CLIENT +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_WORDPRESS_CLIENT +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.UriLogin import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalOverlayUtil import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalPhaseHelper import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginHelper @@ -53,6 +59,11 @@ import javax.inject.Inject import javax.inject.Named import org.wordpress.android.ui.mysite.cards.applicationpassword.ApplicationPasswordViewModelSlice import org.wordpress.android.ui.posts.GutenbergKitWarmupHelper +import org.wordpress.android.ui.utils.UiString +import org.wordpress.android.util.AppLog +import rs.wordpress.api.kotlin.WpRequestResult +import uniffi.wp_api.ApplicationPasswordCreateParams +import uniffi.wp_api.WpUuid @Suppress("LargeClass", "LongMethod", "LongParameterList") class MySiteViewModel @Inject constructor( @@ -81,6 +92,9 @@ class MySiteViewModel @Inject constructor( private val dashboardItemsViewModelSlice: DashboardItemsViewModelSlice, private val applicationPasswordViewModelSlice: ApplicationPasswordViewModelSlice, private val gutenbergKitWarmupHelper: GutenbergKitWarmupHelper, + private val wpApiClientProvider: WpApiClientProvider, + private val applicationPasswordLoginHelper: ApplicationPasswordLoginHelper, + private val appLogWrapper: AppLogWrapper, ) : ScopedViewModel(mainDispatcher) { private val _onSnackbarMessage = MutableLiveData>() private val _onNavigation = MutableLiveData>() @@ -438,6 +452,69 @@ class MySiteViewModel @Inject constructor( ) } + fun createApplicationPassword(site: SiteModel) { + viewModelScope.launch { + val client = wpApiClientProvider.getWpApiClientCookiesNonceAuthentication( + site = site, + ) + val appName = if (buildConfigWrapper.isJetpackApp) { + ANDROID_JETPACK_CLIENT + } else { + ANDROID_WORDPRESS_CLIENT + } + val appId = WpUuid() + val response = client.request { requestBuilder -> + requestBuilder.applicationPasswords().createForCurrentUser( + params = ApplicationPasswordCreateParams( + appId = appId.uuidString(), + name = "$appName-${System.currentTimeMillis()}" + ) + ) + } + when (response) { + is WpRequestResult.Success -> { + val name = site.username // This should be the response name, but it's retuning a wrong value + val password = response.response.data.password + val apiRootUrl = wpApiClientProvider.getApiRootUrlFrom(site) + applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom( + UriLogin( + siteUrl = site.url, + user = name, + password = password, + apiRootUrl = apiRootUrl + ) + ) + // Hide the Application Password creation card + applicationPasswordViewModelSlice.uiModelMutable.postValue(null) + _onSnackbarMessage.postValue( + Event( + SnackbarMessageHolder( + UiString.UiStringResWithParams( + R.string.application_password_credentials_stored, + UiString.UiStringText(site.url) + ) + ) + ) + ) + } + + else -> { + appLogWrapper.e(AppLog.T.API, "Error creating application password") + _onSnackbarMessage.postValue( + Event( + SnackbarMessageHolder( + UiString.UiStringResWithParams( + R.string.application_password_credentials_storing_error, + UiString.UiStringText(site.url) + ) + ) + ) + ) + } + } + } + } + // FluxC events @Subscribe(threadMode = MAIN) fun onPostUploaded(event: OnPostUploaded) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/SiteNavigationAction.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/SiteNavigationAction.kt index 444a2828fb41..7da21cf6d829 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/SiteNavigationAction.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/SiteNavigationAction.kt @@ -96,6 +96,7 @@ sealed class SiteNavigationAction { object OpenApplicationPasswordsList : SiteNavigationAction() data class OpenApplicationPasswordAuthentication(val url: String) : SiteNavigationAction() + data class OpenApplicationPasswordAutoAuthentication(val site: SiteModel) : SiteNavigationAction() } sealed class BloggingPromptCardNavigationAction: SiteNavigationAction() { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index 8bd557eec486..13546a820f39 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -7,7 +7,6 @@ import kotlinx.coroutines.launch import org.wordpress.android.R import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider -import org.wordpress.android.fluxc.network.rest.wpcom.auth.AppSecrets import org.wordpress.android.fluxc.store.SiteStore import org.wordpress.android.fluxc.utils.AppLogWrapper import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper @@ -25,21 +24,16 @@ import org.wordpress.android.ui.utils.UiString import org.wordpress.android.ui.utils.UiString.UiStringRes import org.wordpress.android.util.AppLog import org.wordpress.android.util.BuildConfigWrapper -import org.wordpress.android.util.getEmailValidationMessage import org.wordpress.android.viewmodel.Event import rs.wordpress.api.kotlin.WpRequestResult import uniffi.wp_api.ApplicationPasswordCreateParams import uniffi.wp_api.WpUuid import javax.inject.Inject -import kotlin.String class ApplicationPasswordViewModelSlice @Inject constructor( private val applicationPasswordLoginHelper: ApplicationPasswordLoginHelper, - private val wpApiClientProvider: WpApiClientProvider, private val siteStore: SiteStore, private val experimentalFeatures: ExperimentalFeatures, - private val buildConfigWrapper: BuildConfigWrapper, - private val appLogWrapper: AppLogWrapper, ) { lateinit var scope: CoroutineScope @@ -81,17 +75,17 @@ class ApplicationPasswordViewModelSlice @Inject constructor( if (authorizationUrlComplete.isEmpty()) { uiModelMutable.postValue(null) } else { - postAuthenticationUrl(site) + showApplicationPasswordCreateCard(site) } } } - private fun postAuthenticationUrl(site: SiteModel) { + private fun showApplicationPasswordCreateCard(site: SiteModel) { uiModelMutable.postValue( MySiteCardAndItem.Card.QuickLinksItem( listOf( QuickLinkItem( - label = UiString.UiStringRes(R.string.application_password_title), + label = UiStringRes(R.string.application_password_title), icon = R.drawable.ic_lock_white_24dp, onClick = ListItemInteraction.create { onClick(site) } ) @@ -102,62 +96,10 @@ class ApplicationPasswordViewModelSlice @Inject constructor( private fun onClick(site: SiteModel) { - scope.launch { - val client = wpApiClientProvider.getWpApiClientCookiesNonceAuthentication( - site = site, + _onNavigation.postValue( + Event( + SiteNavigationAction.OpenApplicationPasswordAutoAuthentication(site) ) - val appName = if (buildConfigWrapper.isJetpackApp) { - ANDROID_JETPACK_CLIENT - } else { - ANDROID_WORDPRESS_CLIENT - } - val appId = WpUuid() - val response = client.request { requestBuilder -> - requestBuilder.applicationPasswords().createForCurrentUser( - params = ApplicationPasswordCreateParams( - appId = appId.uuidString(), - name = "$appName-${System.currentTimeMillis()}" - ) - ) - } - when (response) { - is WpRequestResult.Success -> { - val name = site.username // This should be the response name, but it's retuning a wrong value - val password = response.response.data.password - val apiRootUrl = wpApiClientProvider.getApiRootUrlFrom(site) - applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom( - UriLogin( - siteUrl = site.url, - user = name, - password = password, - apiRootUrl = apiRootUrl - ) - ) - _onSnackbarMessage.postValue( - Event( - SnackbarMessageHolder( - UiString.UiStringResWithParams(R.string.application_password_credentials_stored, - UiString.UiStringText(site.url) - ) - ) - ) - ) - } - - else -> { - appLogWrapper.e(AppLog.T.API, "Error creating application password") - _onSnackbarMessage.postValue( - Event( - SnackbarMessageHolder( - UiString.UiStringResWithParams( - R.string.application_password_credentials_storing_error, - UiString.UiStringText(site.url) - ) - ) - ) - ) - } - } - } + ) } } From 32cf37029cb8fd32d8d85c0d9322baa2c189a664 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 17 Nov 2025 13:26:33 +0100 Subject: [PATCH 18/23] Using a dialog for AP creation --- WordPress/src/main/AndroidManifest.xml | 4 + ...plicationPasswordAutoAuthDialogActivity.kt | 179 ++++++++++++++++++ ...licationPasswordAutoAuthDialogViewModel.kt | 99 ++++++++++ .../android/ui/mysite/MySiteFragment.kt | 30 ++- .../android/ui/mysite/MySiteViewModel.kt | 80 +++----- 5 files changed, 334 insertions(+), 58 deletions(-) create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogActivity.kt create mode 100644 WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml index 3fe82298701c..f6bdfdc55b21 100644 --- a/WordPress/src/main/AndroidManifest.xml +++ b/WordPress/src/main/AndroidManifest.xml @@ -151,6 +151,10 @@ android:name=".ui.accounts.login.applicationpassword.ApplicationPasswordRequiredDialogActivity" android:theme="@style/WordPress.TransparentDialog" android:exported="false" /> + = android.os.Build.VERSION_CODES.TIRAMISU) { + intent.getParcelableExtra(EXTRA_SITE, SiteModel::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra(EXTRA_SITE) + } + + if (site == null) { + finish() + return + } + + // Observe navigation events + lifecycleScope.launch { + viewModel.navigationEvent.collect { event -> + when (event) { + is ApplicationPasswordAutoAuthDialogViewModel.NavigationEvent.Success -> { + setResult(RESULT_SUCCESS) + finish() + } + is ApplicationPasswordAutoAuthDialogViewModel.NavigationEvent.Error -> { + setResult(RESULT_ERROR) + finish() + } + } + } + } + + setContent { + AppThemeM3 { + val isLoading = viewModel.isLoading.collectAsState() + ApplicationPasswordAutoAuthDialog( + isLoading = isLoading.value, + onDismiss = { finish() }, + onConfirm = { viewModel.createApplicationPassword(site) } + ) + } + } + } + + companion object { + private const val EXTRA_SITE = "extra_site" + const val RESULT_SUCCESS = Activity.RESULT_OK + const val RESULT_ERROR = Activity.RESULT_FIRST_USER + const val RESULT_DISMISSED = Activity.RESULT_CANCELED + + fun createIntent(context: Context, site: SiteModel): Intent { + return Intent(context, ApplicationPasswordAutoAuthDialogActivity::class.java).apply { + putExtra(EXTRA_SITE, site) + } + } + } +} + +@Composable +fun ApplicationPasswordAutoAuthDialog( + isLoading: Boolean, + onDismiss: () -> Unit, + onConfirm: () -> Unit, +) { + var showMore by rememberSaveable { mutableStateOf(false) } + + AlertDialog( + onDismissRequest = { if (!isLoading) onDismiss() }, + icon = { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + modifier = Modifier.size(Margin.ExtraLarge.value) + ) + }, + title = { Text(text = stringResource(R.string.application_password_info_title)) }, + text = { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .padding(vertical = Margin.Small.value) + ) { + Text(text = stringResource(R.string.application_password_info_description_1)) + + if (!showMore) { + Spacer(modifier = Modifier.height(Margin.Medium.value)) + Text( + text = stringResource(R.string.learn_more), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, + modifier = Modifier + .clickable { showMore = true } + .padding(vertical = Margin.Small.value) + ) + } else { + Spacer(modifier = Modifier.height(Margin.Medium.value)) + Text(text = stringResource(R.string.application_password_info_description_2)) + Spacer(modifier = Modifier.height(Margin.Medium.value)) + Text(text = stringResource(R.string.application_password_info_description_3)) + Spacer(modifier = Modifier.height(Margin.Medium.value)) + Text(text = stringResource(R.string.application_password_info_description_4)) + } + } + }, + confirmButton = { + Button( + onClick = onConfirm, + enabled = !isLoading + ) { + if (isLoading) { + CircularProgressIndicator( + modifier = Modifier.size(16.dp), + strokeWidth = 2.dp + ) + } else { + Text(text = stringResource(R.string.enable)) + } + } + }, + dismissButton = { + TextButton( + onClick = onDismiss, + enabled = !isLoading + ) { + Text(text = stringResource(R.string.cancel)) + } + } + ) +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt new file mode 100644 index 000000000000..647f9c22d174 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt @@ -0,0 +1,99 @@ +package org.wordpress.android.ui.accounts.login.applicationpassword + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import org.wordpress.android.R +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider +import org.wordpress.android.fluxc.utils.AppLogWrapper +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_JETPACK_CLIENT +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_WORDPRESS_CLIENT +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.UriLogin +import org.wordpress.android.ui.pages.SnackbarMessageHolder +import org.wordpress.android.ui.utils.UiString +import org.wordpress.android.util.AppLog +import org.wordpress.android.util.BuildConfigWrapper +import org.wordpress.android.viewmodel.Event +import rs.wordpress.api.kotlin.WpRequestResult +import uniffi.wp_api.ApplicationPasswordCreateParams +import uniffi.wp_api.WpUuid +import javax.inject.Inject + +@HiltViewModel +class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( + private val wpApiClientProvider: WpApiClientProvider, + private val applicationPasswordLoginHelper: ApplicationPasswordLoginHelper, + private val buildConfigWrapper: BuildConfigWrapper, + private val appLogWrapper: AppLogWrapper, +) : ViewModel() { + private val _navigationEvent = MutableSharedFlow() + val navigationEvent: SharedFlow = _navigationEvent.asSharedFlow() + + private val _isLoading = MutableStateFlow(false) + val isLoading: StateFlow = _isLoading.asStateFlow() + + fun createApplicationPassword(site: SiteModel) { + viewModelScope.launch { + try { + _isLoading.value = true + val client = wpApiClientProvider.getWpApiClientCookiesNonceAuthentication( + site = site, + ) + val appName = if (buildConfigWrapper.isJetpackApp) { + ANDROID_JETPACK_CLIENT + } else { + ANDROID_WORDPRESS_CLIENT + } + val appId = WpUuid() + val response = client.request { requestBuilder -> + requestBuilder.applicationPasswords().createForCurrentUser( + params = ApplicationPasswordCreateParams( + appId = appId.uuidString(), + name = "$appName-${System.currentTimeMillis()}" + ) + ) + } + when (response) { + is WpRequestResult.Success -> { + val name = site.username // This should be the response name, but it's retuning a wrong value + val password = response.response.data.password + val apiRootUrl = wpApiClientProvider.getApiRootUrlFrom(site) + applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom( + UriLogin( + siteUrl = site.url, + user = name, + password = password, + apiRootUrl = apiRootUrl + ) + ) + _navigationEvent.emit(NavigationEvent.Success) + } + + else -> { + appLogWrapper.e(AppLog.T.API, "Error creating application password") + _navigationEvent.emit(NavigationEvent.Error) + } + } + } catch (e: Exception) { + appLogWrapper.e(AppLog.T.API, "Exception creating application password: ${e.message}") + _navigationEvent.emit(NavigationEvent.Error) + } finally { + _isLoading.value = false + } + } + } + + sealed class NavigationEvent { + object Success : NavigationEvent() + object Error : NavigationEvent() + } +} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt index 76b80476ad3f..e8d3f0ce136d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteFragment.kt @@ -8,6 +8,7 @@ import android.os.Bundle import android.os.Parcelable import android.view.View import android.view.WindowManager +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.StringRes import androidx.core.text.HtmlCompat import androidx.core.view.isVisible @@ -21,6 +22,7 @@ import org.wordpress.android.R import org.wordpress.android.WordPress import org.wordpress.android.analytics.AnalyticsTracker import org.wordpress.android.databinding.MySiteFragmentBinding +import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.store.AccountStore import org.wordpress.android.fluxc.store.QuickStartStore import org.wordpress.android.ui.ActivityLauncher @@ -31,6 +33,7 @@ import org.wordpress.android.ui.RequestCodes import org.wordpress.android.ui.TextInputDialogFragment import org.wordpress.android.ui.WPWebViewActivity import org.wordpress.android.ui.accounts.LoginEpilogueActivity +import org.wordpress.android.ui.accounts.login.applicationpassword.ApplicationPasswordAutoAuthDialogActivity import org.wordpress.android.ui.bloganuary.learnmore.BloganuaryNudgeLearnMoreOverlayFragment import org.wordpress.android.ui.deeplinks.DeepLinkingIntentReceiverActivity import org.wordpress.android.ui.domains.DomainRegistrationActivity @@ -147,6 +150,26 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment), private var binding: MySiteFragmentBinding? = null private var siteTitle: String? = null + private var pendingApplicationPasswordSite: SiteModel? = null + + private val applicationPasswordAutoAuthLauncher = registerForActivityResult( + ActivityResultContracts.StartActivityForResult() + ) { result -> + pendingApplicationPasswordSite?.let { site -> + when (result.resultCode) { + ApplicationPasswordAutoAuthDialogActivity.RESULT_SUCCESS -> { + viewModel.onApplicationPasswordCreated(site) + } + ApplicationPasswordAutoAuthDialogActivity.RESULT_ERROR -> { + viewModel.onApplicationPasswordCreationError(site) + } + ApplicationPasswordAutoAuthDialogActivity.RESULT_DISMISSED -> { + // User dismissed the dialog, no action needed + } + } + pendingApplicationPasswordSite = null + } + } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -742,7 +765,12 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment), activityNavigator.openApplicationPasswordLogin(requireActivity(), action.url) } is SiteNavigationAction.OpenApplicationPasswordAutoAuthentication -> { - viewModel.createApplicationPassword(action.site) + pendingApplicationPasswordSite = action.site + val intent = ApplicationPasswordAutoAuthDialogActivity.createIntent( + requireContext(), + action.site + ) + applicationPasswordAutoAuthLauncher.launch(intent) } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt index 0ee7f475f396..b4465592391e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt @@ -452,67 +452,33 @@ class MySiteViewModel @Inject constructor( ) } - fun createApplicationPassword(site: SiteModel) { - viewModelScope.launch { - val client = wpApiClientProvider.getWpApiClientCookiesNonceAuthentication( - site = site, - ) - val appName = if (buildConfigWrapper.isJetpackApp) { - ANDROID_JETPACK_CLIENT - } else { - ANDROID_WORDPRESS_CLIENT - } - val appId = WpUuid() - val response = client.request { requestBuilder -> - requestBuilder.applicationPasswords().createForCurrentUser( - params = ApplicationPasswordCreateParams( - appId = appId.uuidString(), - name = "$appName-${System.currentTimeMillis()}" + fun onApplicationPasswordCreated(site: SiteModel) { + // Hide the Application Password creation card + applicationPasswordViewModelSlice.uiModelMutable.postValue(null) + _onSnackbarMessage.postValue( + Event( + SnackbarMessageHolder( + UiString.UiStringResWithParams( + R.string.application_password_credentials_stored, + UiString.UiStringText(site.url) ) ) - } - when (response) { - is WpRequestResult.Success -> { - val name = site.username // This should be the response name, but it's retuning a wrong value - val password = response.response.data.password - val apiRootUrl = wpApiClientProvider.getApiRootUrlFrom(site) - applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom( - UriLogin( - siteUrl = site.url, - user = name, - password = password, - apiRootUrl = apiRootUrl - ) - ) - // Hide the Application Password creation card - applicationPasswordViewModelSlice.uiModelMutable.postValue(null) - _onSnackbarMessage.postValue( - Event( - SnackbarMessageHolder( - UiString.UiStringResWithParams( - R.string.application_password_credentials_stored, - UiString.UiStringText(site.url) - ) - ) - ) - ) - } + ) + ) + } - else -> { - appLogWrapper.e(AppLog.T.API, "Error creating application password") - _onSnackbarMessage.postValue( - Event( - SnackbarMessageHolder( - UiString.UiStringResWithParams( - R.string.application_password_credentials_storing_error, - UiString.UiStringText(site.url) - ) - ) - ) + fun onApplicationPasswordCreationError(site: SiteModel) { + _onSnackbarMessage.postValue( + Event( + SnackbarMessageHolder( + UiString.UiStringResWithParams( + R.string.application_password_credentials_storing_error, + UiString.UiStringText(site.url) ) - } - } - } + ) + ) + ) + } // FluxC events From 5bb07928253da697af4fcc86a0a10d66fe00f2f4 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 17 Nov 2025 13:28:28 +0100 Subject: [PATCH 19/23] String change --- .../ApplicationPasswordAutoAuthDialogActivity.kt | 9 ++++----- WordPress/src/main/res/values/strings.xml | 1 + 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogActivity.kt index 6e124df207e1..f6aeb82256ab 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogActivity.kt @@ -1,6 +1,5 @@ package org.wordpress.android.ui.accounts.login.applicationpassword -import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle @@ -92,9 +91,9 @@ class ApplicationPasswordAutoAuthDialogActivity : ComponentActivity() { companion object { private const val EXTRA_SITE = "extra_site" - const val RESULT_SUCCESS = Activity.RESULT_OK - const val RESULT_ERROR = Activity.RESULT_FIRST_USER - const val RESULT_DISMISSED = Activity.RESULT_CANCELED + const val RESULT_SUCCESS = 0 + const val RESULT_ERROR = -1 + const val RESULT_DISMISSED = 1 fun createIntent(context: Context, site: SiteModel): Intent { return Intent(context, ApplicationPasswordAutoAuthDialogActivity::class.java).apply { @@ -163,7 +162,7 @@ fun ApplicationPasswordAutoAuthDialog( strokeWidth = 2.dp ) } else { - Text(text = stringResource(R.string.enable)) + Text(text = stringResource(R.string.create)) } } }, diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 2e88ae646f6b..6df3418d8b4c 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -5093,6 +5093,7 @@ translators: %s: Select control option value e.g: "Auto, 25%". --> You can view and manage these passwords in your user profile within your WordPress site\'s admin panel. Please note that revoking application passwords used by the app will terminate the app\'s access to your site. Be cautious when revoking application passwords created by the app. Enable + Create Never used Name: Created: From b210131c579a99e59ff8dbc28bd2672abe36aed1 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 17 Nov 2025 13:30:44 +0100 Subject: [PATCH 20/23] detekt and style --- .../ApplicationPasswordAutoAuthDialogViewModel.kt | 5 +---- .../wordpress/android/ui/mysite/MySiteViewModel.kt | 14 -------------- .../ApplicationPasswordViewModelSlice.kt | 11 ----------- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt index 647f9c22d174..3ba806c36aef 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch -import org.wordpress.android.R import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider import org.wordpress.android.fluxc.utils.AppLogWrapper @@ -18,11 +17,8 @@ import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_JETPACK_CLIENT import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_WORDPRESS_CLIENT import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.UriLogin -import org.wordpress.android.ui.pages.SnackbarMessageHolder -import org.wordpress.android.ui.utils.UiString import org.wordpress.android.util.AppLog import org.wordpress.android.util.BuildConfigWrapper -import org.wordpress.android.viewmodel.Event import rs.wordpress.api.kotlin.WpRequestResult import uniffi.wp_api.ApplicationPasswordCreateParams import uniffi.wp_api.WpUuid @@ -41,6 +37,7 @@ class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( private val _isLoading = MutableStateFlow(false) val isLoading: StateFlow = _isLoading.asStateFlow() + @Suppress("TooGenericExceptionCaught") fun createApplicationPassword(site: SiteModel) { viewModelScope.launch { try { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt index b4465592391e..351e4a238f99 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/MySiteViewModel.kt @@ -17,17 +17,11 @@ import org.wordpress.android.R import org.wordpress.android.analytics.AnalyticsTracker.Stat import org.wordpress.android.fluxc.Dispatcher import org.wordpress.android.fluxc.model.SiteModel -import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider import org.wordpress.android.fluxc.store.AccountStore import org.wordpress.android.fluxc.store.PostStore.OnPostUploaded import org.wordpress.android.fluxc.store.QuickStartStore.QuickStartTask -import org.wordpress.android.fluxc.utils.AppLogWrapper import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.modules.UI_THREAD -import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper -import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_JETPACK_CLIENT -import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_WORDPRESS_CLIENT -import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.UriLogin import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalOverlayUtil import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalPhaseHelper import org.wordpress.android.ui.jetpackoverlay.individualplugin.WPJetpackIndividualPluginHelper @@ -60,10 +54,6 @@ import javax.inject.Named import org.wordpress.android.ui.mysite.cards.applicationpassword.ApplicationPasswordViewModelSlice import org.wordpress.android.ui.posts.GutenbergKitWarmupHelper import org.wordpress.android.ui.utils.UiString -import org.wordpress.android.util.AppLog -import rs.wordpress.api.kotlin.WpRequestResult -import uniffi.wp_api.ApplicationPasswordCreateParams -import uniffi.wp_api.WpUuid @Suppress("LargeClass", "LongMethod", "LongParameterList") class MySiteViewModel @Inject constructor( @@ -92,9 +82,6 @@ class MySiteViewModel @Inject constructor( private val dashboardItemsViewModelSlice: DashboardItemsViewModelSlice, private val applicationPasswordViewModelSlice: ApplicationPasswordViewModelSlice, private val gutenbergKitWarmupHelper: GutenbergKitWarmupHelper, - private val wpApiClientProvider: WpApiClientProvider, - private val applicationPasswordLoginHelper: ApplicationPasswordLoginHelper, - private val appLogWrapper: AppLogWrapper, ) : ScopedViewModel(mainDispatcher) { private val _onSnackbarMessage = MutableLiveData>() private val _onNavigation = MutableLiveData>() @@ -478,7 +465,6 @@ class MySiteViewModel @Inject constructor( ) ) ) - } // FluxC events diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt index 13546a820f39..e1f6821f371f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/cards/applicationpassword/ApplicationPasswordViewModelSlice.kt @@ -6,13 +6,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import org.wordpress.android.R import org.wordpress.android.fluxc.model.SiteModel -import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider import org.wordpress.android.fluxc.store.SiteStore -import org.wordpress.android.fluxc.utils.AppLogWrapper import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper -import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_JETPACK_CLIENT -import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.Companion.ANDROID_WORDPRESS_CLIENT -import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper.UriLogin import org.wordpress.android.ui.mysite.MySiteCardAndItem import org.wordpress.android.ui.mysite.MySiteCardAndItem.Card.QuickLinksItem.QuickLinkItem import org.wordpress.android.ui.mysite.SiteNavigationAction @@ -20,14 +15,8 @@ import org.wordpress.android.ui.pages.SnackbarMessageHolder import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures.Feature import org.wordpress.android.ui.utils.ListItemInteraction -import org.wordpress.android.ui.utils.UiString import org.wordpress.android.ui.utils.UiString.UiStringRes -import org.wordpress.android.util.AppLog -import org.wordpress.android.util.BuildConfigWrapper import org.wordpress.android.viewmodel.Event -import rs.wordpress.api.kotlin.WpRequestResult -import uniffi.wp_api.ApplicationPasswordCreateParams -import uniffi.wp_api.WpUuid import javax.inject.Inject class ApplicationPasswordViewModelSlice @Inject constructor( From 77e533bd2df78275447f0ea0ace3b0a7ea45d4e9 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 17 Nov 2025 14:25:51 +0100 Subject: [PATCH 21/23] Claude PR suggestions --- .../ApplicationPasswordAutoAuthDialogActivity.kt | 12 +++++++----- .../ApplicationPasswordAutoAuthDialogViewModel.kt | 11 +++++++++-- .../network/rest/wpapi/rs/WpApiClientProvider.kt | 1 + 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogActivity.kt index f6aeb82256ab..a7c6f89ee474 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogActivity.kt @@ -82,7 +82,10 @@ class ApplicationPasswordAutoAuthDialogActivity : ComponentActivity() { val isLoading = viewModel.isLoading.collectAsState() ApplicationPasswordAutoAuthDialog( isLoading = isLoading.value, - onDismiss = { finish() }, + onDismiss = { + setResult(RESULT_DISMISSED) + finish() + }, onConfirm = { viewModel.createApplicationPassword(site) } ) } @@ -91,8 +94,8 @@ class ApplicationPasswordAutoAuthDialogActivity : ComponentActivity() { companion object { private const val EXTRA_SITE = "extra_site" - const val RESULT_SUCCESS = 0 - const val RESULT_ERROR = -1 + const val RESULT_SUCCESS = -1 + const val RESULT_ERROR = -0 const val RESULT_DISMISSED = 1 fun createIntent(context: Context, site: SiteModel): Intent { @@ -168,8 +171,7 @@ fun ApplicationPasswordAutoAuthDialog( }, dismissButton = { TextButton( - onClick = onDismiss, - enabled = !isLoading + onClick = onDismiss ) { Text(text = stringResource(R.string.cancel)) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt index 3ba806c36aef..30b04d7a7250 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt @@ -22,6 +22,9 @@ import org.wordpress.android.util.BuildConfigWrapper import rs.wordpress.api.kotlin.WpRequestResult import uniffi.wp_api.ApplicationPasswordCreateParams import uniffi.wp_api.WpUuid +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale import javax.inject.Inject @HiltViewModel @@ -41,6 +44,9 @@ class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( fun createApplicationPassword(site: SiteModel) { viewModelScope.launch { try { + require(site.username.isNotBlank()) { "Site username is required for cookie authentication" } + require(site.password.isNotBlank()) { "Site password is required for cookie authentication" } + _isLoading.value = true val client = wpApiClientProvider.getWpApiClientCookiesNonceAuthentication( site = site, @@ -55,13 +61,14 @@ class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( requestBuilder.applicationPasswords().createForCurrentUser( params = ApplicationPasswordCreateParams( appId = appId.uuidString(), - name = "$appName-${System.currentTimeMillis()}" + name = + "$appName-${SimpleDateFormat("yyyy-MM-dd_HH:mm", Locale.getDefault()).format(Date())}" ) ) } when (response) { is WpRequestResult.Success -> { - val name = site.username // This should be the response name, but it's retuning a wrong value + val name = site.username val password = response.response.data.password val apiRootUrl = wpApiClientProvider.getApiRootUrlFrom(site) applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom( diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt index 213c37f7246f..c868fbab38e4 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt @@ -43,6 +43,7 @@ class WpApiClientProvider @Inject constructor( // Create OkHttpClient with cookie jar for cookies/nonce authentication val okHttpClient = OkHttpClient.Builder() .cookieJar(object : CookieJar { + // We are storing the cookie in memory as this is a one-time call and there0s no need to persist it private val cookieStore = mutableMapOf>() override fun saveFromResponse(url: HttpUrl, cookies: List) { From 69dcf6ecb941f56bc4564bd88bb95decaf386236 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 17 Nov 2025 14:52:44 +0100 Subject: [PATCH 22/23] Adding and fixing tests --- .../repository/AIBotSupportRepositoryTest.kt | 8 +- ...tionPasswordAutoAuthDialogViewModelTest.kt | 151 ++++++++++++++++++ 2 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 WordPress/src/test/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModelTest.kt diff --git a/WordPress/src/test/java/org/wordpress/android/support/aibot/repository/AIBotSupportRepositoryTest.kt b/WordPress/src/test/java/org/wordpress/android/support/aibot/repository/AIBotSupportRepositoryTest.kt index e4a5f401f93b..0aff080b9dae 100644 --- a/WordPress/src/test/java/org/wordpress/android/support/aibot/repository/AIBotSupportRepositoryTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/support/aibot/repository/AIBotSupportRepositoryTest.kt @@ -22,7 +22,7 @@ import uniffi.wp_api.MessageContext import uniffi.wp_api.SupportBotsRequestAddMessageToBotConversationResponse import uniffi.wp_api.SupportBotsRequestCreateBotConversationResponse import uniffi.wp_api.SupportBotsRequestGetBotConversationResponse -import uniffi.wp_api.SupportBotsRequestGetBotConverationListResponse +import uniffi.wp_api.SupportBotsRequestGetBotConversationListResponse import uniffi.wp_api.UserMessageContext import uniffi.wp_api.UserPaidSupportEligibility import uniffi.wp_api.WpNetworkHeaderMap @@ -64,7 +64,7 @@ class AIBotSupportRepositoryTest : BaseUnitTest() { ) // Create the actual response type - val response = SupportBotsRequestGetBotConverationListResponse( + val response = SupportBotsRequestGetBotConversationListResponse( data = testConversations, headerMap = mock() ) @@ -72,7 +72,7 @@ class AIBotSupportRepositoryTest : BaseUnitTest() { val successResponse = WpRequestResult.Success(response = response) repository.init(testAccessToken, testUserId) - whenever(wpComApiClient.request(any())) + whenever(wpComApiClient.request(any())) .thenReturn(successResponse) val result = repository.loadConversations() @@ -268,7 +268,7 @@ class AIBotSupportRepositoryTest : BaseUnitTest() { return BotConversationSummary( chatId = chatId.toULong(), createdAt = Date(), - lastMessage = BotMessageSummary( + summaryMessage = BotMessageSummary( content = message, createdAt = Date(), role = "user" diff --git a/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModelTest.kt new file mode 100644 index 000000000000..095c077046b6 --- /dev/null +++ b/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModelTest.kt @@ -0,0 +1,151 @@ +package org.wordpress.android.ui.accounts.login.applicationpassword + +import app.cash.turbine.test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.mockito.Mock +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.any +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.eq +import org.mockito.kotlin.times +import org.mockito.kotlin.verify +import org.mockito.kotlin.whenever +import org.wordpress.android.BaseUnitTest +import org.wordpress.android.fluxc.model.SiteModel +import org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider +import org.wordpress.android.fluxc.utils.AppLogWrapper +import org.wordpress.android.ui.accounts.login.ApplicationPasswordLoginHelper +import org.wordpress.android.util.BuildConfigWrapper +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@ExperimentalCoroutinesApi +class ApplicationPasswordAutoAuthDialogViewModelTest : BaseUnitTest() { + @Mock + lateinit var wpApiClientProvider: WpApiClientProvider + + @Mock + lateinit var applicationPasswordLoginHelper: ApplicationPasswordLoginHelper + + @Mock + lateinit var buildConfigWrapper: BuildConfigWrapper + + @Mock + lateinit var appLogWrapper: AppLogWrapper + + private lateinit var viewModel: ApplicationPasswordAutoAuthDialogViewModel + + private val testSite = SiteModel().apply { + url = "https://example.com" + username = "testuser" + password = "testpass123" + } + + @Before + fun setUp() { + MockitoAnnotations.openMocks(this) + viewModel = ApplicationPasswordAutoAuthDialogViewModel( + wpApiClientProvider, + applicationPasswordLoginHelper, + buildConfigWrapper, + appLogWrapper + ) + } + + @Test + fun `createApplicationPassword with exception during API call emits Error`() = runTest { + // Given + val testException = RuntimeException("API client creation failed") + whenever(wpApiClientProvider.getWpApiClientCookiesNonceAuthentication(eq(testSite))) + .doThrow(testException) + + // When & Then + viewModel.navigationEvent.test { + viewModel.isLoading.test { + // Initially not loading + assertFalse(awaitItem()) + + viewModel.createApplicationPassword(testSite) + + // Should become loading + assertTrue(awaitItem()) + + // Should stop loading even when exception occurs + assertFalse(awaitItem()) + + cancelAndIgnoreRemainingEvents() + } + + // Should emit error event + val navigationEvent = awaitItem() + assertEquals( + ApplicationPasswordAutoAuthDialogViewModel.NavigationEvent.Error, + navigationEvent + ) + + // Should log error with exception message + verify(appLogWrapper, times(1)).e(any(), any()) + + // Should NOT store credentials + verify(applicationPasswordLoginHelper, times(0)).storeApplicationPasswordCredentialsFrom(any()) + + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `createApplicationPassword with blank username emits Error`() = runTest { + // Given + val invalidSite = testSite.apply { username = "" } + + // When & Then + viewModel.navigationEvent.test { + viewModel.createApplicationPassword(invalidSite) + + // Should emit error event + val navigationEvent = awaitItem() + assertEquals( + ApplicationPasswordAutoAuthDialogViewModel.NavigationEvent.Error, + navigationEvent + ) + + // Should log error + verify(appLogWrapper, times(1)).e(any(), any()) + + // Should NOT make API call + verify(wpApiClientProvider, times(0)).getWpApiClientCookiesNonceAuthentication(any()) + + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `createApplicationPassword with blank password emits Error`() = runTest { + // Given + val invalidSite = testSite.apply { password = "" } + + // When & Then + viewModel.navigationEvent.test { + viewModel.createApplicationPassword(invalidSite) + + // Should emit error event + val navigationEvent = awaitItem() + assertEquals( + ApplicationPasswordAutoAuthDialogViewModel.NavigationEvent.Error, + navigationEvent + ) + + // Should log error + verify(appLogWrapper, times(1)).e(any(), any()) + + // Should NOT make API call + verify(wpApiClientProvider, times(0)).getWpApiClientCookiesNonceAuthentication(any()) + + cancelAndIgnoreRemainingEvents() + } + } +} From f948a83f829484a35a62c60a1e91dfc9fa1b063a Mon Sep 17 00:00:00 2001 From: Adalberto Plaza Date: Thu, 20 Nov 2025 09:34:47 +0100 Subject: [PATCH 23/23] Update libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt Co-authored-by: David Calhoun --- .../android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt index c868fbab38e4..b4b351a890fa 100644 --- a/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt +++ b/libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/rs/WpApiClientProvider.kt @@ -43,7 +43,7 @@ class WpApiClientProvider @Inject constructor( // Create OkHttpClient with cookie jar for cookies/nonce authentication val okHttpClient = OkHttpClient.Builder() .cookieJar(object : CookieJar { - // We are storing the cookie in memory as this is a one-time call and there0s no need to persist it + // We are storing the cookie in memory as this is a one-time call and there is no need to persist it private val cookieStore = mutableMapOf>() override fun saveFromResponse(url: HttpUrl, cookies: List) {