From aac8ffd632c28cdbf38cf484eddb58a47333b3b3 Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Mon, 3 Aug 2026 10:54:19 -0400 Subject: [PATCH 1/6] Remove RS Unified Comments experimental toggle; auto-enable for eligible sites The RS comments screens now show automatically for any site with an application password, mirroring how RS Posts and Pages work. Removes the Feature.RS_UNIFIED_COMMENTS toggle and its strings; the RS comments UI itself is unchanged. The gate no longer needs a Context, which leaves ActivityLauncherEntryPoint without consumers, so it's deleted too. --- .../android/ui/ActivityLauncher.java | 21 ++++++------------- .../android/ui/ActivityLauncherEntryPoint.kt | 17 --------------- .../unified/UnifiedCommentListFragment.kt | 6 +++--- .../NotificationsDetailActivity.java | 2 +- .../ExperimentalFeatures.kt | 5 ----- WordPress/src/main/res/values/strings.xml | 2 -- 6 files changed, 10 insertions(+), 43 deletions(-) delete mode 100644 WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncherEntryPoint.kt diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index d7f15c1cea2e..f79cca4c2fac 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -146,8 +146,6 @@ import java.util.List; import java.util.Map; -import dagger.hilt.android.EntryPointAccessors; - import static org.wordpress.android.analytics.AnalyticsTracker.ACTIVITY_LOG_ACTIVITY_ID_KEY; import static org.wordpress.android.analytics.AnalyticsTracker.Stat.POST_LIST_ACCESS_ERROR; import static org.wordpress.android.analytics.AnalyticsTracker.Stat.READER_ARTICLE_DETAIL_REBLOGGED; @@ -738,7 +736,7 @@ public static void viewPageParentForResult(@NonNull Fragment fragment, @NonNull public static void viewUnifiedComments(Context context, SiteModel site) { Intent intent; - if (shouldUseRsComments(context, site)) { + if (shouldUseRsComments(site)) { intent = CommentsRsListActivity.Companion.createIntent(context); } else { intent = new Intent(context, UnifiedCommentsActivity.class); @@ -752,18 +750,11 @@ public static void viewUnifiedComments(Context context, SiteModel site) { * Whether the wordpress-rs comments screens (list, and the notification-hosted detail) should * be used for {@code site} instead of the legacy (FluxC) ones. */ - public static boolean shouldUseRsComments(@NonNull Context context, @NonNull SiteModel site) { - // Match the RS posts/pages gate: use the rs comments screens only for self-hosted sites - // with an application password, and keep the legacy (FluxC) comments screens everywhere - // else (including WP.com-accessed sites). - if (!site.hasApplicationPassword()) { - return false; - } - ActivityLauncherEntryPoint entryPoint = EntryPointAccessors.fromApplication( - context.getApplicationContext(), - ActivityLauncherEntryPoint.class - ); - return entryPoint.experimentalFeatures().isEnabled(Feature.RS_UNIFIED_COMMENTS); + public static boolean shouldUseRsComments(@Nullable SiteModel site) { + // Match the RS posts/pages gate: use the rs comments screens only for sites with an + // application password, and keep the legacy (FluxC) comments screens everywhere else + // (including WP.com-accessed sites). + return site != null && site.hasApplicationPassword(); } public static void viewCurrentBlogThemes(Context context, SiteModel site) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncherEntryPoint.kt b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncherEntryPoint.kt deleted file mode 100644 index 02bbb0a89b10..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncherEntryPoint.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.wordpress.android.ui - -import dagger.hilt.EntryPoint -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures - -/** - * Hilt entry point for static [ActivityLauncher] helpers that need access to singleton - * dependencies. Used to gate activity routing decisions (e.g. the modern pages list flag) - * without threading dependencies through every caller. - */ -@EntryPoint -@InstallIn(SingletonComponent::class) -interface ActivityLauncherEntryPoint { - fun experimentalFeatures(): ExperimentalFeatures -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/comments/unified/UnifiedCommentListFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/comments/unified/UnifiedCommentListFragment.kt index 9893a0f21d35..e92dcf96ba46 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/comments/unified/UnifiedCommentListFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/comments/unified/UnifiedCommentListFragment.kt @@ -160,9 +160,9 @@ class UnifiedCommentListFragment : Fragment(R.layout.unified_comment_list_fragme private fun showCommentDetails(commentId: Long, commentStatus: CommentStatus) { currentSnackbar?.dismiss() - // This legacy list is only reached when the RS_UNIFIED_COMMENTS flag is off or the site - // can't use wordpress-rs (see ActivityLauncher.viewUnifiedComments), so it always pairs - // with the legacy detail; the rs list launches the rs detail itself. + // This legacy list is only reached when the site can't use wordpress-rs (see + // ActivityLauncher.viewUnifiedComments), so it always pairs with the legacy detail; + // the rs list launches the rs detail itself. commentDetails.launch( CommentDetailsActivityRequest( commentId, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java index f0254a11daa2..6ac3ed3601f5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java @@ -407,7 +407,7 @@ private Fragment createDetailFragmentForNote(@NonNull Note note) { SiteModel site = mSiteStore.getSiteBySiteId(note.getSiteId()); // The rs detail needs a real site from the SiteStore; the legacy fragment can fall // back to a dummy WP.com site built from the note, so it stays the catch-all. - if (site != null && note.getCommentId() != 0 && ActivityLauncher.shouldUseRsComments(this, site)) { + if (site != null && note.getCommentId() != 0 && ActivityLauncher.shouldUseRsComments(site)) { fragment = UnifiedCommentDetailsFragment.newInstance( site, note.getCommentId(), diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/experimentalfeatures/ExperimentalFeatures.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/experimentalfeatures/ExperimentalFeatures.kt index a6e60ad1c8da..da80cc20cf47 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/experimentalfeatures/ExperimentalFeatures.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/experimentalfeatures/ExperimentalFeatures.kt @@ -39,11 +39,6 @@ class ExperimentalFeatures @Inject constructor( "experimental_post_types", R.string.experimental_post_types, R.string.experimental_post_types_description - ), - RS_UNIFIED_COMMENTS( - "rs_unified_comments", - R.string.experimental_rs_comments, - R.string.experimental_rs_comments_description ); } } diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 499e06525928..72a3d2d66515 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -1028,8 +1028,6 @@ Trends at a Glance Every metric shows how it\'s trending compared to the previous period, so you always know if you\'re growing. You can turn off New Stats from the overflow menu in the Stats screen. - RS Unified Comments - Show comments in a more modern UI for WordPress.com and self-hosted sites with application passwords Failed to load post Failed to load page Copy URL From 70da90f151aeaf414a50e21cb7d150ea9cab9d2b Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Mon, 3 Aug 2026 12:59:51 -0400 Subject: [PATCH 2/6] Correct the shouldUseRsComments comment about WP.com sites The comment claimed the legacy comments screens are kept for WP.com-accessed sites, but hasApplicationPassword() has no WP.com check, so Atomic and Jetpack sites that hold an application password do get the rs screens. Only the comment was wrong; the routing is unchanged. --- .../java/org/wordpress/android/ui/ActivityLauncher.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index f79cca4c2fac..88b0503e8bd1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -751,9 +751,10 @@ public static void viewUnifiedComments(Context context, SiteModel site) { * be used for {@code site} instead of the legacy (FluxC) ones. */ public static boolean shouldUseRsComments(@Nullable SiteModel site) { - // Match the RS posts/pages gate: use the rs comments screens only for sites with an - // application password, and keep the legacy (FluxC) comments screens everywhere else - // (including WP.com-accessed sites). + // Match the RS posts/pages gate: use the rs comments screens for any site with an + // application password. That includes WP.com-accessed sites, since Atomic and Jetpack + // sites can hold one (only simple WP.com sites can't). Everything else keeps the + // legacy (FluxC) comments screens. return site != null && site.hasApplicationPassword(); } From 39bc77e946be698e7957362a1e246f24eedf150d Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Mon, 3 Aug 2026 13:11:36 -0400 Subject: [PATCH 3/6] Extract the shared wordpress-rs eligibility check into SiteUtils.canUseWpRs The posts, pages, and comments gates had drifted into three byte-identical copies of the same predicate, coupled only by a comment asking future editors to keep them in sync. One definition means they can't diverge for the same site. --- .../android/ui/ActivityLauncher.java | 27 +++---------------- .../NotificationsDetailActivity.java | 3 ++- .../org/wordpress/android/util/SiteUtils.java | 12 +++++++++ 3 files changed, 18 insertions(+), 24 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index 88b0503e8bd1..ec5483e0a1c5 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -132,6 +132,7 @@ import org.wordpress.android.ui.utils.PreMigrationDeepLinkData; import org.wordpress.android.util.AppLog; import org.wordpress.android.util.AppLog.T; +import org.wordpress.android.util.SiteUtils; import org.wordpress.android.util.ToastUtils; import org.wordpress.android.util.UriWrapper; import org.wordpress.android.util.UrlUtils; @@ -644,12 +645,8 @@ public static void viewConnectJetpackForStats(Context context, SiteModel site) { context.startActivity(intent); } - private static boolean shouldUseNewPostList(@Nullable SiteModel site) { - return site != null && site.hasApplicationPassword(); - } - public static void viewCurrentBlogPosts(Context context, SiteModel site) { - if (shouldUseNewPostList(site)) { + if (SiteUtils.canUseWpRs(site)) { context.startActivity(PostRsListActivity.Companion.createIntent(context)); return; } @@ -685,7 +682,7 @@ public static void viewCurrentBlogMedia(Context context, SiteModel site) { } public static void viewCurrentBlogPages(@NonNull Context context, @NonNull SiteModel site) { - if (shouldUseNewPagesList(site)) { + if (SiteUtils.canUseWpRs(site)) { context.startActivity(PagesRsListActivity.Companion.createIntent(context)); AnalyticsUtils.trackWithSiteDetails(AnalyticsTracker.Stat.OPENED_PAGES, site); return; @@ -696,10 +693,6 @@ public static void viewCurrentBlogPages(@NonNull Context context, @NonNull SiteM AnalyticsUtils.trackWithSiteDetails(AnalyticsTracker.Stat.OPENED_PAGES, site); } - private static boolean shouldUseNewPagesList(@Nullable SiteModel site) { - return site != null && site.hasApplicationPassword(); - } - public static void viewPostTypes(@NonNull Context context, @NonNull SiteModel site) { SiteReference siteRef = SiteReference.Companion.create( site.getSiteId(), @@ -736,7 +729,7 @@ public static void viewPageParentForResult(@NonNull Fragment fragment, @NonNull public static void viewUnifiedComments(Context context, SiteModel site) { Intent intent; - if (shouldUseRsComments(site)) { + if (SiteUtils.canUseWpRs(site)) { intent = CommentsRsListActivity.Companion.createIntent(context); } else { intent = new Intent(context, UnifiedCommentsActivity.class); @@ -746,18 +739,6 @@ public static void viewUnifiedComments(Context context, SiteModel site) { AnalyticsUtils.trackWithSiteDetails(AnalyticsTracker.Stat.OPENED_COMMENTS, site); } - /** - * Whether the wordpress-rs comments screens (list, and the notification-hosted detail) should - * be used for {@code site} instead of the legacy (FluxC) ones. - */ - public static boolean shouldUseRsComments(@Nullable SiteModel site) { - // Match the RS posts/pages gate: use the rs comments screens for any site with an - // application password. That includes WP.com-accessed sites, since Atomic and Jetpack - // sites can hold one (only simple WP.com sites can't). Everything else keeps the - // legacy (FluxC) comments screens. - return site != null && site.hasApplicationPassword(); - } - public static void viewCurrentBlogThemes(Context context, SiteModel site) { Intent intent = new Intent(context, ThemeBrowserActivity.class); intent.putExtra(WordPress.SITE, site); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java index 6ac3ed3601f5..df96e011abdd 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsDetailActivity.java @@ -59,6 +59,7 @@ import org.wordpress.android.ui.stats.StatsViewType; import org.wordpress.android.ui.stats.refresh.utils.StatsLaunchedFrom; import org.wordpress.android.util.AppLog; +import org.wordpress.android.util.SiteUtils; import org.wordpress.android.util.StringUtils; import org.wordpress.android.util.ToastUtils; import org.wordpress.android.util.analytics.AnalyticsUtils; @@ -407,7 +408,7 @@ private Fragment createDetailFragmentForNote(@NonNull Note note) { SiteModel site = mSiteStore.getSiteBySiteId(note.getSiteId()); // The rs detail needs a real site from the SiteStore; the legacy fragment can fall // back to a dummy WP.com site built from the note, so it stays the catch-all. - if (site != null && note.getCommentId() != 0 && ActivityLauncher.shouldUseRsComments(site)) { + if (site != null && note.getCommentId() != 0 && SiteUtils.canUseWpRs(site)) { fragment = UnifiedCommentDetailsFragment.newInstance( site, note.getCommentId(), diff --git a/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java b/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java index 2b01cb78d876..2fe0d426e003 100644 --- a/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java @@ -261,6 +261,18 @@ public static boolean isAccessedViaWPComRest(@NonNull SiteModel site) { return site.getOrigin() == SiteModel.ORIGIN_WPCOM_REST; } + /** + * Whether the wordpress-rs screens (posts, pages, comments) should be used for {@code site} + * instead of the legacy (FluxC) ones. Keep this as the single definition so those screens + * can't drift apart for the same site. + *

+ * An application password is the requirement. That includes WP.com-accessed sites, since + * Atomic and Jetpack sites can hold one (only simple WP.com sites can't). + */ + public static boolean canUseWpRs(@Nullable SiteModel site) { + return site != null && site.hasApplicationPassword(); + } + public static String getSiteIconUrl(SiteModel site, int size) { return PhotonUtils.getPhotonImageUrl(site.getIconUrl(), size, size, PhotonUtils.Quality.HIGH, site.isPrivateWPComAtomic()); From e05c8a54afe80173f360a6e0a96d5fa5f60e7346 Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Mon, 3 Aug 2026 13:14:52 -0400 Subject: [PATCH 4/6] Drop the write-only SITE extra from the legacy comments Intent UnifiedCommentsActivity never reads WordPress.SITE - it resolves the site from SelectedSiteRepository, as does the rs list - so the extra was a trap for anyone trying to fix a multi-site bug by changing the caller. --- .../org/wordpress/android/ui/ActivityLauncher.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java index ec5483e0a1c5..7b72a062061f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java @@ -728,13 +728,10 @@ public static void viewPageParentForResult(@NonNull Fragment fragment, @NonNull } public static void viewUnifiedComments(Context context, SiteModel site) { - Intent intent; - if (SiteUtils.canUseWpRs(site)) { - intent = CommentsRsListActivity.Companion.createIntent(context); - } else { - intent = new Intent(context, UnifiedCommentsActivity.class); - intent.putExtra(WordPress.SITE, site); - } + // Neither screen reads the site from the Intent; both resolve it from SelectedSiteRepository. + Intent intent = SiteUtils.canUseWpRs(site) + ? CommentsRsListActivity.Companion.createIntent(context) + : new Intent(context, UnifiedCommentsActivity.class); context.startActivity(intent); AnalyticsUtils.trackWithSiteDetails(AnalyticsTracker.Stat.OPENED_COMMENTS, site); } From 86e7d443c28e69c770d34644997ec9c87c688ba9 Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Mon, 3 Aug 2026 13:14:52 -0400 Subject: [PATCH 5/6] Add tests for SiteUtils.canUseWpRs Pins the rs eligibility rule in both directions, including that a WP.com site holding an application password is eligible - the case the old comment got wrong. --- .../wordpress/android/util/SiteUtilsTest.kt | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/WordPress/src/test/java/org/wordpress/android/util/SiteUtilsTest.kt b/WordPress/src/test/java/org/wordpress/android/util/SiteUtilsTest.kt index fc3450a3d262..2456697d25c7 100644 --- a/WordPress/src/test/java/org/wordpress/android/util/SiteUtilsTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/util/SiteUtilsTest.kt @@ -192,4 +192,51 @@ class SiteUtilsTest { val circularSiteImage = SiteUtils.getSiteImageType(false, CIRCULAR) assertThat(circularSiteImage).isEqualTo(BLAVATAR_CIRCULAR) } + + @Test + fun `canUseWpRs returns false for a null site`() { + assertThat(SiteUtils.canUseWpRs(null)).isFalse() + } + + @Test + fun `canUseWpRs returns false when the site has no application password`() { + val site = SiteModel() + + assertThat(SiteUtils.canUseWpRs(site)).isFalse() + } + + @Test + fun `canUseWpRs returns false when only one half of the credentials is stored`() { + val site = SiteModel() + site.apiRestUsernamePlain = "user" + + assertThat(SiteUtils.canUseWpRs(site)).isFalse() + + site.apiRestUsernamePlain = null + site.apiRestPasswordPlain = "password" + + assertThat(SiteUtils.canUseWpRs(site)).isFalse() + } + + @Test + fun `canUseWpRs returns true when the site has an application password`() { + val site = SiteModel() + site.apiRestUsernamePlain = "user" + site.apiRestPasswordPlain = "password" + + assertThat(SiteUtils.canUseWpRs(site)).isTrue() + } + + @Test + fun `canUseWpRs returns true for a WPCom site with an application password`() { + val site = SiteModel() + site.setIsWPCom(true) + site.origin = SiteModel.ORIGIN_WPCOM_REST + site.apiRestUsernamePlain = "user" + site.apiRestPasswordPlain = "password" + + // Atomic and Jetpack sites can hold an application password, so being reachable over the + // WP.com REST API does not keep a site on the legacy screens. + assertThat(SiteUtils.canUseWpRs(site)).isTrue() + } } From 855bea781fad7bca58cdf147a342a60ef41490cc Mon Sep 17 00:00:00 2001 From: Nick Bradbury Date: Mon, 3 Aug 2026 13:29:55 -0400 Subject: [PATCH 6/6] Drop the now-redundant semicolon after the last Feature entry Removing RS_UNIFIED_COMMENTS left the enum terminator on the new last entry, where it falsely signals that members follow. The enum has none. --- .../ui/prefs/experimentalfeatures/ExperimentalFeatures.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/experimentalfeatures/ExperimentalFeatures.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/experimentalfeatures/ExperimentalFeatures.kt index da80cc20cf47..e1153dca6014 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/experimentalfeatures/ExperimentalFeatures.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/experimentalfeatures/ExperimentalFeatures.kt @@ -39,6 +39,6 @@ class ExperimentalFeatures @Inject constructor( "experimental_post_types", R.string.experimental_post_types, R.string.experimental_post_types_description - ); + ) } }