Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions WordPress/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ android {
buildConfigField "boolean", "DYNAMIC_DASHBOARD_CARDS", "false"
buildConfigField "boolean", "STATS_TRAFFIC_SUBSCRIBERS_TABS", "false"
buildConfigField "boolean", "NEW_STATS", "false"
buildConfigField "boolean", "WP_COM_WP_RS", "false"
buildConfigField "boolean", "READER_READING_PREFERENCES", "false"
buildConfigField "boolean", "READER_READING_PREFERENCES_FEEDBACK", "false"
buildConfigField "boolean", "READER_TAGS_FEED", "false"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
import org.wordpress.android.ui.qrcodeauth.QRCodeAuthActivity;
import org.wordpress.android.ui.reader.ReaderActivityLauncher;
import org.wordpress.android.ui.reader.ReaderConstants;
import org.wordpress.android.ui.rs.WpRsRouting;
import org.wordpress.android.ui.selfhostedusers.SelfHostedUsersActivity;
import org.wordpress.android.ui.sitecreation.SiteCreationActivity;
import org.wordpress.android.ui.sitecreation.misc.SiteCreationSource;
Expand All @@ -131,7 +132,6 @@
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;
Expand All @@ -146,6 +146,10 @@
import java.util.List;
import java.util.Map;

import dagger.hilt.InstallIn;
import dagger.hilt.android.EntryPointAccessors;
import dagger.hilt.components.SingletonComponent;

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;
Expand Down Expand Up @@ -641,8 +645,15 @@ public static void viewConnectJetpackForStats(Context context, SiteModel site) {
context.startActivity(intent);
}

private static boolean canUseWpRs(@NonNull Context context, @Nullable SiteModel site) {
return EntryPointAccessors
.fromApplication(context.getApplicationContext(), ActivityLauncherEntryPoint.class)
.wpRsRouting()
.canUseWpRs(site);
}

public static void viewCurrentBlogPosts(Context context, SiteModel site) {
if (SiteUtils.canUseWpRs(site)) {
if (canUseWpRs(context, site)) {
context.startActivity(PostRsListActivity.Companion.createIntent(context));
return;
}
Expand Down Expand Up @@ -678,7 +689,7 @@ public static void viewCurrentBlogMedia(Context context, SiteModel site) {
}

public static void viewCurrentBlogPages(@NonNull Context context, @NonNull SiteModel site) {
if (SiteUtils.canUseWpRs(site)) {
if (canUseWpRs(context, site)) {
context.startActivity(PagesRsListActivity.Companion.createIntent(context));
AnalyticsUtils.trackWithSiteDetails(AnalyticsTracker.Stat.OPENED_PAGES, site);
return;
Expand Down Expand Up @@ -725,7 +736,7 @@ public static void viewPageParentForResult(@NonNull Fragment fragment, @NonNull

public static void viewUnifiedComments(Context context, SiteModel site) {
// Neither screen reads the site from the Intent; both resolve it from SelectedSiteRepository.
Intent intent = SiteUtils.canUseWpRs(site)
Intent intent = canUseWpRs(context, site)
? CommentsRsListActivity.Companion.createIntent(context)
: new Intent(context, UnifiedCommentsActivity.class);
context.startActivity(intent);
Expand Down Expand Up @@ -1885,4 +1896,11 @@ public static void showTermsList(@NonNull Context context, @NonNull String taxon
Intent intent = TermsDataViewActivity.Companion.getIntent(context, taxonomySlug, taxonomyName, isHierarchical);
context.startActivity(intent);
}

// Fully qualified because PostUtils.EntryPoint is already imported under the simple name.
@dagger.hilt.EntryPoint
@InstallIn(SingletonComponent.class)
interface ActivityLauncherEntryPoint {
WpRsRouting wpRsRouting();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import org.wordpress.android.ui.comments.unified.extension.isNotEqualTo
import org.wordpress.android.ui.comments.unified.usecase.GetCommentUseCase
import org.wordpress.android.ui.notifications.utils.NotificationsActionsWrapper
import org.wordpress.android.ui.pages.SnackbarMessageHolder
import org.wordpress.android.ui.rs.WpRsRouting
import org.wordpress.android.ui.utils.UiString
import org.wordpress.android.ui.utils.UiString.UiStringRes
import org.wordpress.android.util.NetworkUtilsWrapper
Expand All @@ -55,7 +56,8 @@ class UnifiedCommentsEditViewModel @Inject constructor(
private val getCommentUseCase: GetCommentUseCase,
private val notificationActionsWrapper: NotificationsActionsWrapper,
private val readerCommentTableWrapper: ReaderCommentTableWrapper,
private val analyticsUtilsWrapper: AnalyticsUtilsWrapper
private val analyticsUtilsWrapper: AnalyticsUtilsWrapper,
private val wpRsRouting: WpRsRouting
) : ScopedViewModel(mainDispatcher) {
private val _uiState = MutableLiveData<EditCommentUiState>()
private val _uiActionEvent = MutableLiveData<Event<EditCommentActionEvent>>()
Expand Down Expand Up @@ -254,7 +256,7 @@ class UnifiedCommentsEditViewModel @Inject constructor(
private suspend fun mapCommentEssentials(): CommentEssentials {
// A failed load returns default CommentEssentials, which fails isValid() and surfaces
// the load-error snackbar in initViews().
val essentials = if (canUseRs()) loadCommentViaRs() else loadCommentViaFluxC()
val essentials = if (wpRsRouting.canUseWpRs(site)) loadCommentViaRs() else loadCommentViaFluxC()
return essentials ?: CommentEssentials()
}

Expand Down Expand Up @@ -287,7 +289,7 @@ class UnifiedCommentsEditViewModel @Inject constructor(
// application-password sites (FluxC's updateEditComment can't reach app-password sites).
// Fall back to FluxC for sites rs can't serve — e.g. XML-RPC-only self-hosted comments
// still reachable through the legacy detail/reader launch points.
val saved = if (canUseRs()) {
val saved = if (wpRsRouting.canUseWpRs(site)) {
updateCommentViaRs(editedCommentEssentials)
} else {
updateCommentViaFluxC(editedCommentEssentials)
Expand Down Expand Up @@ -328,8 +330,6 @@ class UnifiedCommentsEditViewModel @Inject constructor(
return !commentsStore.updateEditComment(site, updatedComment).isError
}

private fun canUseRs(): Boolean = site.isUsingWpComRestApi || site.hasApplicationPassword()

/**
* Saves the edit through wordpress-rs and, on success, mirrors the SERVER's resulting state
* into the FluxC cache so the still-FluxC comment list/notifications reflect the change —
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@
import org.wordpress.android.ui.reader.ReaderPostDetailFragment;
import org.wordpress.android.ui.reader.comments.ThreadedCommentsActionSource;
import org.wordpress.android.ui.reader.tracker.ReaderTracker;
import org.wordpress.android.ui.rs.WpRsRouting;
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;
Expand Down Expand Up @@ -102,6 +102,7 @@ public class NotificationsDetailActivity extends BaseAppCompatActivity implement
@Inject ReaderTracker mReaderTracker;
@Inject LikesEnhancementsFeatureConfig mLikesEnhancementsFeatureConfig;
@Inject ListScenarioUtils mListScenarioUtils;
@Inject WpRsRouting mWpRsRouting;

@Nullable private String mNoteId;
private boolean mIsTappedOnNotification;
Expand Down Expand Up @@ -408,7 +409,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 && SiteUtils.canUseWpRs(site)) {
if (site != null && note.getCommentId() != 0 && mWpRsRouting.canUseWpRs(site)) {
fragment = UnifiedCommentDetailsFragment.newInstance(
site,
note.getCommentId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ class PostRsFluxCBridge @Inject constructor(
* to exist in FluxC's local database. If [lastModified] is
* provided and differs from the cached row's
* `remoteLastModified`, the cache is considered
* stale and the post is re-fetched from the server.
* stale and the post is re-fetched from the server —
* unless the row holds unsynced local edits, which the
* fetch would otherwise overwrite.
*
* @throws IllegalStateException if the post cannot be fetched or inserted.
*/
Expand All @@ -41,11 +43,12 @@ class PostRsFluxCBridge @Inject constructor(
site: SiteModel,
lastModified: String? = null
): PostModel {
// Fast path — already in FluxC and still fresh
// Fast path — already in FluxC and either still fresh
// or holding local changes the editor must not lose
postStore.getPostByRemotePostId(remotePostId, site)?.let { cached ->
if (lastModified == null ||
lastModified == cached.remoteLastModified
) {
val fresh = lastModified == null ||
lastModified == cached.remoteLastModified
if (fresh || cached.isLocallyChanged) {
return cached
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,10 @@ class WpServiceProvider @Inject constructor(
}

private fun createService(site: SiteModel): WpService {
val delegate = createDelegate(site)
val useWpCom = site.useWpCom()
val delegate = createDelegate(site, useWpCom)
val wpApiCache = getOrCreateCache()
val siteInfo = if (site.isWPCom) {
val siteInfo = if (useWpCom) {
SiteInfo.WordPressCom(siteId = site.siteId.toULong())
} else {
val apiRoot = site.wpApiRestUrl?.takeIf { it.isNotEmpty() }
Expand All @@ -64,8 +65,16 @@ class WpServiceProvider @Inject constructor(
return WpService(siteInfo, delegate, wpApiCache.cache)
}

private fun createDelegate(site: SiteModel): WpApiClientDelegate {
val authProvider = if (site.isWPCom) {
/**
* Whether the site is served over the WP.com REST API with an OAuth bearer token rather than
* directly with an application password. Deliberately the same rule as
* [org.wordpress.android.fluxc.network.rest.wpapi.rs.WpApiClientProvider.getWpApiClient] so the
* list and the edit-time fetch that follows it can't disagree about the transport.
*/
private fun SiteModel.useWpCom(): Boolean = isWPCom || isUsingWpComRestApi

private fun createDelegate(site: SiteModel, useWpCom: Boolean): WpApiClientDelegate {
val authProvider = if (useWpCom) {
createWpComAuthProvider(accountStore)
} else {
val username = site.apiRestUsernamePlain
Expand Down
29 changes: 29 additions & 0 deletions WordPress/src/main/java/org/wordpress/android/ui/rs/WpRsRouting.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.wordpress.android.ui.rs

import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.util.config.WpComWpRsFeatureConfig
import javax.inject.Inject
import javax.inject.Singleton

/**
* Decides whether the wordpress-rs screens (posts, pages, comments) are used for a site instead of
* the legacy (FluxC) ones. Keep this as the single definition so those screens can't drift apart
* for the same site.
*
* Note this does not govern every route into those screens: the drafts/scheduled tabs, upload
* notifications and blogging reminders go straight to the legacy screens regardless of this
* decision.
*/
@Singleton
class WpRsRouting @Inject constructor(
private val wpComWpRsFeatureConfig: WpComWpRsFeatureConfig
) {
/**
* An application password is enough on its own. Sites reached over the WP.com REST API don't
* need one — wordpress-rs authenticates them with an OAuth bearer token — but they're behind a
* rollout flag because that moves the whole WP.com audience off the mature legacy screens.
*/
fun canUseWpRs(site: SiteModel?): Boolean = site != null &&
(site.hasApplicationPassword() ||
(site.isUsingWpComRestApi && wpComWpRsFeatureConfig.isEnabled()))
}
12 changes: 0 additions & 12 deletions WordPress/src/main/java/org/wordpress/android/util/SiteUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -261,18 +261,6 @@ 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.
* <p>
* 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());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.wordpress.android.util.config

import org.wordpress.android.BuildConfig
import org.wordpress.android.annotation.Feature
import org.wordpress.android.util.config.WpComWpRsFeatureConfig.Companion.WP_COM_WP_RS_REMOTE_FIELD
import javax.inject.Inject

/**
* Configuration for serving the wordpress-rs screens (posts, pages, comments) to sites reached over
* the WP.com REST API. Those sites authenticate with an OAuth bearer token rather than an
* application password, so this rolls them out gradually rather than moving the whole WP.com
* audience off the legacy screens at once.
*/
@Feature(WP_COM_WP_RS_REMOTE_FIELD, false)
class WpComWpRsFeatureConfig @Inject constructor(
appConfig: AppConfig
) : FeatureConfig(
appConfig,
BuildConfig.WP_COM_WP_RS,
WP_COM_WP_RS_REMOTE_FIELD,
) {
companion object {
const val WP_COM_WP_RS_REMOTE_FIELD = "android_wp_rs_wpcom"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import org.wordpress.android.ui.comments.unified.UnifiedCommentsEditViewModel.Fi
import org.wordpress.android.ui.comments.unified.usecase.GetCommentUseCase
import org.wordpress.android.ui.notifications.utils.NotificationsActionsWrapper
import org.wordpress.android.ui.pages.SnackbarMessageHolder
import org.wordpress.android.ui.rs.WpRsRouting
import org.wordpress.android.ui.utils.UiString.UiStringRes
import org.wordpress.android.util.NetworkUtilsWrapper
import org.wordpress.android.util.analytics.AnalyticsUtils.AnalyticsCommentActionSource
Expand Down Expand Up @@ -82,6 +83,9 @@ class UnifiedCommentsEditViewModelTest : BaseUnitTest() {
@Mock
lateinit var analyticsUtilsWrapper: AnalyticsUtilsWrapper

@Mock
lateinit var wpRsRouting: WpRsRouting

private lateinit var viewModel: UnifiedCommentsEditViewModel

private var uiState: MutableList<EditCommentUiState> = mutableListOf()
Expand All @@ -93,7 +97,7 @@ class UnifiedCommentsEditViewModelTest : BaseUnitTest() {
siteId = REMOTE_SITE_ID
}

// A WP.com site is served by wordpress-rs (isUsingWpComRestApi() is true), unlike [site].
// A site wordpress-rs serves, unlike [site] — see the wpRsRouting stub in setup().
private val rsSite = SiteModel().apply {
id = LOCAL_SITE_ID
siteId = REMOTE_SITE_ID
Expand All @@ -117,6 +121,8 @@ class UnifiedCommentsEditViewModelTest : BaseUnitTest() {
whenever(readerCommentTableWrapper.getComment(REMOTE_SITE_ID, postId, remoteCommentId))
.thenReturn(READER_COMMENT_ENTITY)

whenever(wpRsRouting.canUseWpRs(rsSite)).thenReturn(true)

viewModel = UnifiedCommentsEditViewModel(
mainDispatcher = testDispatcher(),
bgDispatcher = testDispatcher(),
Expand All @@ -128,7 +134,8 @@ class UnifiedCommentsEditViewModelTest : BaseUnitTest() {
getCommentUseCase = getCommentUseCase,
notificationActionsWrapper = notificationActionsWrapper,
readerCommentTableWrapper = readerCommentTableWrapper,
analyticsUtilsWrapper
analyticsUtilsWrapper = analyticsUtilsWrapper,
wpRsRouting = wpRsRouting
)

setupObservers()
Expand Down
Loading
Loading