From 7b8283498a7dac2b70758010882a234eb968f1ae Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Thu, 13 Aug 2026 09:48:56 -0400 Subject: [PATCH 1/4] CMM-2301: Always show the Me row when the bottom nav is hidden The Me row in My Site exists to replace the bottom navigation's Me tab when that nav is hidden, but it was gated on site capabilities instead. Both flags it checked are permanently false for sites added over the application-password / WP-API path: hasCapabilityManageOptions is only populated from the WP.com REST response, and isSelfHostedAdmin only from XML-RPC wp.getUsersBlogs. Such users had no route to Me at all, and so no Account Settings, App Settings, Experimental Features or Help & Support. Gate the row on exactly the condition WPMainActivity uses to hide the nav. Me is account-level, so site capabilities play no part. This also drops the duplicate row that appeared for self-hosted admins while the nav was still visible, which b4450f8daab already intended to remove. MeFragment now gates its toolbar on the host activity rather than the removal phase, so it can't render a title-less, back-button-less screen. --- RELEASE-NOTES.txt | 4 ++++ .../wordpress/android/ui/main/MeFragment.kt | 8 +++---- .../mysite/items/listitem/SiteItemsBuilder.kt | 2 +- .../items/listitem/SiteListItemBuilder.kt | 15 ++++++------- .../ui/mysite/items/SiteItemFixtures.kt | 7 ++++++ .../items/listitem/SiteListItemBuilderTest.kt | 22 +++++++++++++++++++ 6 files changed, 44 insertions(+), 14 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 02253ea329d2..a1a4dc10fbdc 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,5 +1,9 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] +27.1 +----- +* [**] Fixed the Me screen being unreachable in the WordPress app after signing in to a self-hosted site with an application password. + 27.0 ----- * [*] You can now browse Google Photos (albums, collections, and search) when adding photos or videos from your device. diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt index 95f17d0d9449..fa3174e5a310 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt @@ -52,7 +52,6 @@ import org.wordpress.android.ui.about.UnifiedAboutActivity import org.wordpress.android.ui.accounts.HelpActivity.Origin.ME_SCREEN_HELP import org.wordpress.android.ui.debug.DebugSettingsActivity import org.wordpress.android.ui.deeplinks.DeepLinkOpenWebLinksWithJetpackHelper -import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalOverlayUtil import org.wordpress.android.ui.main.MeViewModel.RecommendAppUiState import org.wordpress.android.ui.main.WPMainActivity.OnScrollToTopListener import org.wordpress.android.ui.main.emailverificationbanner.EmailVerificationBanner @@ -129,9 +128,6 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener { @Inject lateinit var uiHelpers: UiHelpers - @Inject - lateinit var jetpackFeatureRemovalUtils: JetpackFeatureRemovalOverlayUtil - @Inject lateinit var domainManagementFeatureConfig: DomainManagementFeatureConfig @@ -168,7 +164,9 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener { @Suppress("LongMethod") private fun MeFragmentBinding.setupViews() { - if (!BuildConfig.IS_JETPACK_APP && jetpackFeatureRemovalUtils.shouldHideJetpackFeatures()) { + // MeActivity hosts this fragment on its own, so it needs a toolbar; inside the bottom + // navigation's pager the host activity already provides one. + if (requireActivity() is MeActivity) { with(requireActivity() as AppCompatActivity) { setSupportActionBar(toolbarMain) supportActionBar?.apply { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteItemsBuilder.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteItemsBuilder.kt index 2900a2e2b8da..993420ba72a7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteItemsBuilder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteItemsBuilder.kt @@ -132,7 +132,7 @@ class SiteItemsBuilder @Inject constructor( List { return listOfNotNull( siteListItemBuilder.buildDomainsItemIfAvailable(params.site, params.onClick), - siteListItemBuilder.buildMeItemIfAvailable(params.site, params.onClick), + siteListItemBuilder.buildMeItemIfAvailable(params.onClick), siteListItemBuilder.buildSiteSettingsItemIfAvailable(params.site, params.onClick), siteListItemBuilder.buildApplicationPasswordsItemIfAvailable(params.site, params.onClick), ) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt index f719b524e216..c4f4ef0e7b35 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt @@ -195,14 +195,13 @@ class SiteListItemBuilder @Inject constructor( } else null } - @Suppress("ComplexCondition") - fun buildMeItemIfAvailable(site: SiteModel, onClick: (ListItemAction) -> Unit): ListItem? { - return if ((!buildConfigWrapper.isJetpackApp && - jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures() && - site.hasCapabilityManageOptions) || - (!buildConfigWrapper.isJetpackApp && - site.isSelfHostedAdmin) - ) { + /** + * The Me item replaces the bottom navigation's Me tab when that nav is hidden, so it's gated on + * exactly the same condition WPMainActivity uses to hide it. Me is account-level, so site + * capabilities deliberately play no part here. + */ + fun buildMeItemIfAvailable(onClick: (ListItemAction) -> Unit): ListItem? { + return if (jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures()) { ListItem( R.drawable.ic_user_primary_white_24, UiStringRes(R.string.me), diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/SiteItemFixtures.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/SiteItemFixtures.kt index f144d278f891..c77491767c25 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/SiteItemFixtures.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/SiteItemFixtures.kt @@ -113,6 +113,13 @@ val DOMAINS_ITEM = ListItem( onClick = ListItemInteraction.create(DOMAINS, SITE_ITEM_ACTION), listItemAction = DOMAINS ) +val ME_ITEM = ListItem( + R.drawable.ic_user_primary_white_24, + UiStringRes(R.string.me), + onClick = ListItemInteraction.create(ListItemAction.ME, SITE_ITEM_ACTION), + disablePrimaryIconTint = true, + listItemAction = ListItemAction.ME +) val SITE_MONITORING_ITEM = ListItem( R.drawable.gb_ic_tool, UiStringRes(R.string.site_monitoring), diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilderTest.kt index dc155f82181f..e44d74987e0b 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilderTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilderTest.kt @@ -14,6 +14,7 @@ import org.wordpress.android.ui.jetpackoverlay.JetpackFeatureRemovalPhaseHelper import org.wordpress.android.ui.mysite.items.ACTIVITY_ITEM import org.wordpress.android.ui.mysite.items.ADMIN_ITEM import org.wordpress.android.ui.mysite.items.BACKUP_ITEM +import org.wordpress.android.ui.mysite.items.ME_ITEM import org.wordpress.android.ui.mysite.items.PAGES_ITEM import org.wordpress.android.ui.mysite.items.PEOPLE_ITEM import org.wordpress.android.ui.mysite.items.PLUGINS_ITEM @@ -213,6 +214,27 @@ class SiteListItemBuilderTest { whenever(siteModel.hasCapabilityEditPages).thenReturn(canEditPages) } + // The Me item stands in for the hidden bottom navigation, so it must be built for every site + // once Jetpack features are removed - including app-password sites, which report no + // capabilities at all. + @Test + fun `me item built when jetpack features are removed, regardless of site capabilities`() { + whenever(jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures()).thenReturn(true) + + val item = siteListItemBuilder.buildMeItemIfAvailable(SITE_ITEM_ACTION) + + assertThat(item).isEqualTo(ME_ITEM) + } + + @Test + fun `me item not built when jetpack features are not removed`() { + whenever(jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures()).thenReturn(false) + + val item = siteListItemBuilder.buildMeItemIfAvailable(SITE_ITEM_ACTION) + + assertThat(item).isNull() + } + @Test fun `admin item built when site is not WPCom`() { setupAdminItem(isWPCom = false) From 554ee4cf875f11868bd47610143dca7864f04b20 Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Thu, 13 Aug 2026 11:05:20 -0400 Subject: [PATCH 2/4] CMM-2301: Address review feedback Document why buildMeItemIfAvailable needs no explicit Jetpack app check: getCurrentPhase() returns null there, so shouldRemoveJetpackFeatures() is always false. The dependency was invisible at the call site once the explicit guard was removed. Resolve the MeActivity host once instead of calling requireActivity() twice with a redundant cast, which leaves the AppCompatActivity import unused. Drop the release notes entry so this PR doesn't touch the file. --- RELEASE-NOTES.txt | 4 ---- .../main/java/org/wordpress/android/ui/main/MeFragment.kt | 6 +++--- .../android/ui/mysite/items/listitem/SiteListItemBuilder.kt | 5 +++++ 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index a1a4dc10fbdc..02253ea329d2 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -1,9 +1,5 @@ *** PLEASE FOLLOW THIS FORMAT: [] [] -27.1 ------ -* [**] Fixed the Me screen being unreachable in the WordPress app after signing in to a self-hosted site with an application password. - 27.0 ----- * [*] You can now browse Google Photos (albums, collections, and search) when adding photos or videos from your device. diff --git a/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt index fa3174e5a310..f0e6ffd6e4c7 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/main/MeFragment.kt @@ -11,7 +11,6 @@ import android.os.Bundle import android.view.View import android.view.View.OnClickListener import androidx.annotation.StringRes -import androidx.appcompat.app.AppCompatActivity import androidx.compose.runtime.collectAsState import androidx.core.view.isVisible import androidx.fragment.app.Fragment @@ -166,8 +165,9 @@ class MeFragment : Fragment(R.layout.me_fragment), OnScrollToTopListener { private fun MeFragmentBinding.setupViews() { // MeActivity hosts this fragment on its own, so it needs a toolbar; inside the bottom // navigation's pager the host activity already provides one. - if (requireActivity() is MeActivity) { - with(requireActivity() as AppCompatActivity) { + val meActivity = activity as? MeActivity + if (meActivity != null) { + with(meActivity) { setSupportActionBar(toolbarMain) supportActionBar?.apply { setHomeButtonEnabled(true) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt index c4f4ef0e7b35..659dc89b1ca4 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt @@ -199,6 +199,11 @@ class SiteListItemBuilder @Inject constructor( * The Me item replaces the bottom navigation's Me tab when that nav is hidden, so it's gated on * exactly the same condition WPMainActivity uses to hide it. Me is account-level, so site * capabilities deliberately play no part here. + * + * There's no explicit Jetpack app check because there doesn't need to be one: + * [JetpackFeatureRemovalPhaseHelper.getCurrentPhase] returns null for the Jetpack app, so + * [JetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures] is always false there and the + * item is never built. */ fun buildMeItemIfAvailable(onClick: (ListItemAction) -> Unit): ListItem? { return if (jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures()) { From ad4c8a467b28d267bb30dc8108ed06e856a2e16d Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Thu, 13 Aug 2026 11:11:07 -0400 Subject: [PATCH 3/4] CMM-2301: Tighten the Me item docs and test placement Compress the KDoc paragraph on the implicit Jetpack app exclusion, drop a test comment that described a scenario the test can't exercise now that buildMeItemIfAvailable takes no site, and move the Me tests out of the pages group so they sit after the site settings tests, matching the order of the builder's own methods. --- .../items/listitem/SiteListItemBuilder.kt | 6 +-- .../items/listitem/SiteListItemBuilderTest.kt | 41 +++++++++---------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt index 659dc89b1ca4..35290ff3ea14 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt @@ -200,10 +200,8 @@ class SiteListItemBuilder @Inject constructor( * exactly the same condition WPMainActivity uses to hide it. Me is account-level, so site * capabilities deliberately play no part here. * - * There's no explicit Jetpack app check because there doesn't need to be one: - * [JetpackFeatureRemovalPhaseHelper.getCurrentPhase] returns null for the Jetpack app, so - * [JetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures] is always false there and the - * item is never built. + * No explicit Jetpack app check is needed: [JetpackFeatureRemovalPhaseHelper.getCurrentPhase] + * returns null there, so [shouldRemoveJetpackFeatures] is always false and the item never builds. */ fun buildMeItemIfAvailable(onClick: (ListItemAction) -> Unit): ListItem? { return if (jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures()) { diff --git a/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilderTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilderTest.kt index e44d74987e0b..4b557a9d8090 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilderTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilderTest.kt @@ -214,27 +214,6 @@ class SiteListItemBuilderTest { whenever(siteModel.hasCapabilityEditPages).thenReturn(canEditPages) } - // The Me item stands in for the hidden bottom navigation, so it must be built for every site - // once Jetpack features are removed - including app-password sites, which report no - // capabilities at all. - @Test - fun `me item built when jetpack features are removed, regardless of site capabilities`() { - whenever(jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures()).thenReturn(true) - - val item = siteListItemBuilder.buildMeItemIfAvailable(SITE_ITEM_ACTION) - - assertThat(item).isEqualTo(ME_ITEM) - } - - @Test - fun `me item not built when jetpack features are not removed`() { - whenever(jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures()).thenReturn(false) - - val item = siteListItemBuilder.buildMeItemIfAvailable(SITE_ITEM_ACTION) - - assertThat(item).isNull() - } - @Test fun `admin item built when site is not WPCom`() { setupAdminItem(isWPCom = false) @@ -359,6 +338,26 @@ class SiteListItemBuilderTest { assertThat(item).isNull() } + // Me stands in for the bottom navigation once Jetpack features are removed, so it is built + // for every site regardless of capabilities. + @Test + fun `me item built when jetpack features are removed, regardless of site capabilities`() { + whenever(jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures()).thenReturn(true) + + val item = siteListItemBuilder.buildMeItemIfAvailable(SITE_ITEM_ACTION) + + assertThat(item).isEqualTo(ME_ITEM) + } + + @Test + fun `me item not built when jetpack features are not removed`() { + whenever(jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures()).thenReturn(false) + + val item = siteListItemBuilder.buildMeItemIfAvailable(SITE_ITEM_ACTION) + + assertThat(item).isNull() + } + /* SITE MONITORING */ @Test fun `give jetpack app, when FF is true and site is atomic and admin, then site monitoring item is built`() { From 93ebf66fa554b38b7f5107301664005fa31609b2 Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Thu, 13 Aug 2026 11:27:33 -0400 Subject: [PATCH 4/4] CMM-2301: Re-qualify a KDoc link so it resolves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An unqualified [shouldRemoveJetpackFeatures] doesn't resolve from SiteListItemBuilder — the method belongs to JetpackFeatureRemovalPhaseHelper, and importing the class doesn't bring its members into scope. --- .../android/ui/mysite/items/listitem/SiteListItemBuilder.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt b/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt index 35290ff3ea14..489642f31234 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/mysite/items/listitem/SiteListItemBuilder.kt @@ -201,7 +201,8 @@ class SiteListItemBuilder @Inject constructor( * capabilities deliberately play no part here. * * No explicit Jetpack app check is needed: [JetpackFeatureRemovalPhaseHelper.getCurrentPhase] - * returns null there, so [shouldRemoveJetpackFeatures] is always false and the item never builds. + * returns null there, so [JetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures] is + * always false and the item never builds. */ fun buildMeItemIfAvailable(onClick: (ListItemAction) -> Unit): ListItem? { return if (jetpackFeatureRemovalPhaseHelper.shouldRemoveJetpackFeatures()) {