Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,10 @@ private void editComment(@NonNull SiteModel site) {
if (!isAdded()) {
return;
}
// The editor loads and saves the comment over the network, so don't open it offline.
if (!NetworkUtils.checkConnection(getActivity())) {
return;
}
if (mCommentSource != null) {
AnalyticsUtils.trackCommentActionWithSiteDetails(
Stat.COMMENT_EDITOR_OPENED,
Expand Down Expand Up @@ -705,7 +709,7 @@ private CommentIdentifier mapCommentIdentifier() {
switch (mCommentSource) {
case SITE_COMMENTS:
if (mComment != null) {
return new SiteCommentIdentifier(mComment.getId(), mComment.getRemoteCommentId());
return new SiteCommentIdentifier(mComment.getRemoteCommentId());
} else {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ sealed class CommentIdentifier : Parcelable {

@Parcelize
data class SiteCommentIdentifier(
val localCommentId: Int,
override val remoteCommentId: Long
) : CommentIdentifier()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import uniffi.wp_api.CommentDeleteParams
import uniffi.wp_api.CommentListParams
import uniffi.wp_api.CommentRetrieveParams
import uniffi.wp_api.CommentUpdateParams
import uniffi.wp_api.CommentWithEditContext
import uniffi.wp_api.CommentWithViewContext
import uniffi.wp_api.PostEndpointType
import uniffi.wp_api.PostListParams
Expand Down Expand Up @@ -88,6 +89,24 @@ class CommentsRsDataSource @Inject constructor(
}
}

/**
* Fetches a comment's editable state for pre-filling the edit screen. Uses the edit context —
* unlike the view context, it returns the raw (unrendered) content and the author email.
* Requires the `edit_comment` capability; a denial returns null, which the editor surfaces
* as its load-error snackbar.
*/
suspend fun getCommentForEdit(site: SiteModel, commentId: Long): RsEditedComment? = safe(errorValue = null) {
val client = wpApiClientProvider.getWpApiClient(site)
when (
val result = client.request {
it.comments().retrieveWithEditContext(commentId, CommentRetrieveParams())
}
) {
is WpRequestResult.Success -> result.response.data.toRsEditedComment()
else -> null
}
}

/**
* Fetches one page of the site's comments. Pass [firstPageParams] for the first page and the
* previous page's [RsCommentsPageResult.Success.nextPageParams] for subsequent pages.
Expand Down Expand Up @@ -210,9 +229,10 @@ class CommentsRsDataSource @Inject constructor(
data class CommentAuthor(val name: String, val email: String, val url: String)

/**
* The server's post-save state of an edited comment. Mirroring this (rather than the values
* that were sent) into the FluxC cache keeps the cache faithful when the server normalises
* fields — e.g. content passes through KSES filtering.
* A comment's editable state (edit context): the edit screen's pre-fill and the server's
* post-save echo. Mirroring the echo (rather than the values that were sent) into the FluxC
* cache keeps the cache faithful when the server normalises fields — e.g. content passes
* through KSES filtering.
*/
data class RsEditedComment(
val authorName: String,
Expand Down Expand Up @@ -247,22 +267,19 @@ class CommentsRsDataSource @Inject constructor(
)
}
when (result) {
is WpRequestResult.Success -> {
val serverComment = result.response.data
RsEditResult.Success(
RsEditedComment(
authorName = serverComment.authorName,
authorEmail = serverComment.authorEmail,
authorUrl = serverComment.authorUrl,
contentRaw = serverComment.content.raw
)
)
}
is WpRequestResult.Success -> RsEditResult.Success(result.response.data.toRsEditedComment())
is WpRequestResult.WpError -> RsEditResult.Error(result.errorMessage)
else -> RsEditResult.Error(null)
}
}

private fun CommentWithEditContext.toRsEditedComment() = RsEditedComment(
authorName = authorName,
authorEmail = authorEmail,
authorUrl = authorUrl,
contentRaw = content.raw
)

suspend fun delete(site: SiteModel, commentId: Long): RsResult =
write(site) { it.comments().delete(commentId, CommentDeleteParams()) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ import javax.inject.Named
* - **Liking** (wordpress-rs has no comment like action).
* - **Keeping the FluxC-backed comment list in sync**: after each rs write we mirror the change
* into the local comment cache and poke [LocalCommentCacheUpdateHandler] so the (still FluxC)
* list reflects it. The cache row is also the source of the post title, like state and local id
* used to launch the (still FluxC) edit screen.
* list reflects it. The cache row is also the source of the post title and like state.
*
* **Note mode**: when [start] receives a `noteId` the comment was opened from a notification.
* Every successful write then also refreshes the note DB (so the notifications list stays fresh)
Expand Down Expand Up @@ -102,9 +101,6 @@ class UnifiedCommentDetailsViewModel @Inject constructor(
private var loadedComment: RsComment? = null
private var isLikeInProgress = false

// From the FluxC cache row: the local id used to launch the (still FluxC) edit screen.
private var localCommentId: Int = 0

fun start(site: SiteModel, remoteCommentId: Long, noteId: String? = null) {
if (isStarted) return
isStarted = true
Expand Down Expand Up @@ -139,8 +135,8 @@ class UnifiedCommentDetailsViewModel @Inject constructor(
var local = commentsStore.getCommentByLocalSiteAndRemoteId(site.id, remoteCommentId).firstOrNull()
// Opened from the rs list the FluxC cache may not have this comment at all (the
// legacy list guaranteed a row before the detail could open). Fetch it so the
// post title, like state and the edit screen's local id are available — WP.com
// only, since FluxC has no application-password transport.
// post title and like state are available — WP.com only, since FluxC has no
// application-password transport.
if (local == null && site.isUsingWpComRestApi) {
commentsStore.fetchComment(site, remoteCommentId, null)
local = commentsStore.getCommentByLocalSiteAndRemoteId(site.id, remoteCommentId).firstOrNull()
Expand All @@ -165,7 +161,6 @@ class UnifiedCommentDetailsViewModel @Inject constructor(
when {
loaded.rsComment != null -> {
loadedComment = loaded.rsComment
localCommentId = loaded.cached?.id?.toInt() ?: 0
_uiState.value = loaded.rsComment.toUiState(
loaded.cached,
loaded.fallbackPostTitle,
Expand Down Expand Up @@ -232,11 +227,13 @@ class UnifiedCommentDetailsViewModel @Inject constructor(

fun onEditClicked() {
if (loadedComment == null) return
// The editor loads the comment from the network, so don't open it offline.
if (isOffline()) return
trackCommentAction(Stat.COMMENT_EDITOR_OPENED)
// In note mode edit through the note identifier so the edit screen refreshes the note DB
// after saving, keeping the notifications list consistent with the edited comment.
val identifier = noteId?.let { NotificationCommentIdentifier(it, remoteCommentId) }
?: SiteCommentIdentifier(localCommentId, remoteCommentId)
?: SiteCommentIdentifier(remoteCommentId)
_uiActionEvent.value = Event(LaunchEditComment(site, identifier))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import org.wordpress.android.R
import org.wordpress.android.analytics.AnalyticsTracker.Stat.COMMENT_EDITED
import org.wordpress.android.datasets.wrappers.ReaderCommentTableWrapper
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.persistence.comments.CommentsDao.CommentEntity
import org.wordpress.android.fluxc.store.CommentsStore
import org.wordpress.android.models.usecases.LocalCommentCacheUpdateHandler
import org.wordpress.android.modules.BG_THREAD
Expand Down Expand Up @@ -229,70 +228,82 @@ class UnifiedCommentsEditViewModel @Inject constructor(
}

private suspend fun mapCommentEssentials(): CommentEssentials {
val commentEntity = getCommentUseCase.execute(site, commentIdentifier.remoteCommentId)
return if (commentEntity != null) {
// A failed load returns default CommentEssentials, which fails isValid() and surfaces
// the load-error snackbar in initViews().
val essentials = if (canUseRs()) loadCommentViaRs() else loadCommentViaFluxC()
return essentials ?: CommentEssentials()
}

// Edit context returns the raw (unrendered) content the editor must show — the same
// thing the FluxC entity stored — plus the author email the view context omits.
private suspend fun loadCommentViaRs(): CommentEssentials? =
commentsRsDataSource.getCommentForEdit(site, commentIdentifier.remoteCommentId)?.let { rsComment ->
CommentEssentials(
commentId = commentIdentifier.remoteCommentId,
userName = rsComment.authorName,
commentText = rsComment.contentRaw,
userUrl = rsComment.authorUrl,
userEmail = rsComment.authorEmail
)
}

private suspend fun loadCommentViaFluxC(): CommentEssentials? =
getCommentUseCase.execute(site, commentIdentifier.remoteCommentId)?.let { commentEntity ->
CommentEssentials(
commentId = commentEntity.id,
userName = commentEntity.authorName ?: "",
commentText = commentEntity.content ?: "",
userUrl = commentEntity.authorUrl ?: "",
userEmail = commentEntity.authorEmail ?: ""
)
} else {
CommentEssentials()
}
}

private suspend fun updateComment(editedCommentEssentials: CommentEssentials) {
val commentEntity =
commentsStore.getCommentByLocalSiteAndRemoteId(site.id, commentIdentifier.remoteCommentId).firstOrNull()
commentEntity?.run {
val isCommentEntityUpdated = updateCommentEntity(this, editedCommentEssentials)
if (isCommentEntityUpdated) {
analyticsUtilsWrapper.trackCommentActionWithSiteDetails(
COMMENT_EDITED,
commentIdentifier.toCommentActionSource(),
site
)
when (commentIdentifier) {
is NotificationCommentIdentifier -> {
updateNotificationEntity()
}
is ReaderCommentIdentifier -> {
updateReaderEntity(editedCommentEssentials)
}
else -> {
_uiActionEvent.postValue(Event(DONE))
localCommentCacheUpdateHandler.requestCommentsUpdate()
}
}
} else {
showUpdateCommentError()
}
} ?: showUpdateCommentError()
}

private suspend fun updateCommentEntity(
comment: CommentEntity,
editedCommentEssentials: CommentEssentials
): Boolean {
// Prefer wordpress-rs, which can edit comments on both WP.com and self-hosted
// 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.
return if (canUseRs()) {
updateCommentViaRs(comment, editedCommentEssentials)
val saved = if (canUseRs()) {
updateCommentViaRs(editedCommentEssentials)
} else {
val updatedComment = comment.copy(
authorUrl = editedCommentEssentials.userUrl,
authorName = editedCommentEssentials.userName,
authorEmail = editedCommentEssentials.userEmail,
content = editedCommentEssentials.commentText
updateCommentViaFluxC(editedCommentEssentials)
}
if (saved) {
analyticsUtilsWrapper.trackCommentActionWithSiteDetails(
COMMENT_EDITED,
commentIdentifier.toCommentActionSource(),
site
)
!commentsStore.updateEditComment(site, updatedComment).isError
when (commentIdentifier) {
is NotificationCommentIdentifier -> {
updateNotificationEntity()
}
is ReaderCommentIdentifier -> {
updateReaderEntity(editedCommentEssentials)
}
else -> {
_uiActionEvent.postValue(Event(DONE))
localCommentCacheUpdateHandler.requestCommentsUpdate()
}
}
} else {
showUpdateCommentError()
}
}

private suspend fun updateCommentViaFluxC(editedCommentEssentials: CommentEssentials): Boolean {
val comment = commentsStore
.getCommentByLocalSiteAndRemoteId(site.id, commentIdentifier.remoteCommentId)
.firstOrNull() ?: return false
val updatedComment = comment.copy(
authorUrl = editedCommentEssentials.userUrl,
authorName = editedCommentEssentials.userName,
authorEmail = editedCommentEssentials.userEmail,
content = editedCommentEssentials.commentText
)
return !commentsStore.updateEditComment(site, updatedComment).isError
}

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

/**
Expand All @@ -302,11 +313,12 @@ class UnifiedCommentsEditViewModel @Inject constructor(
* server echo (not the values that were sent) is what's cached so server-side normalisation
* (e.g. KSES content filtering) can't diverge from the cache; the legacy FluxC path also
* cached the server response.
*
* The mirror is best-effort: comments opened from the rs list on an application-password
* site may have no FluxC row (FluxC can't fetch there), and the rs screens read the server,
* not the cache.
*/
private suspend fun updateCommentViaRs(
comment: CommentEntity,
editedCommentEssentials: CommentEssentials
): Boolean {
private suspend fun updateCommentViaRs(editedCommentEssentials: CommentEssentials): Boolean {
// The endpoint applies author fields to the comment record for any comment (registered
// author or not), same as the legacy FluxC POST and wp-admin's comment editor.
val result = commentsRsDataSource.updateComment(
Expand All @@ -320,14 +332,19 @@ class UnifiedCommentsEditViewModel @Inject constructor(
)
)
if (result !is RsEditResult.Success) return false
val serverComment = comment.copy(
authorName = result.comment.authorName,
authorEmail = result.comment.authorEmail,
authorUrl = result.comment.authorUrl,
content = result.comment.contentRaw
)
// Local-only cache write (isError = false persists the entity without a network round-trip).
commentsStore.updateComment(isError = false, commentId = serverComment.id, comment = serverComment)
commentsStore
.getCommentByLocalSiteAndRemoteId(site.id, commentIdentifier.remoteCommentId)
.firstOrNull()?.let { cached ->
val serverComment = cached.copy(
authorName = result.comment.authorName,
authorEmail = result.comment.authorEmail,
authorUrl = result.comment.authorUrl,
content = result.comment.contentRaw
)
// Local-only cache write (isError = false persists the entity without a network
// round-trip).
commentsStore.updateComment(isError = false, commentId = serverComment.id, comment = serverComment)
}
return true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,10 @@ class ReaderCommentListActivity : BaseAppCompatActivity(),
comment: ReaderComment,
postSite: SiteModel
) {
// The editor loads and saves the comment over the network, so don't open it offline.
if (!NetworkUtils.checkConnection(this)) {
return
}
val intent = createIntent(
this,
ReaderCommentIdentifier(comment.blogId, comment.postId, comment.commentId),
Expand Down
Loading
Loading