Skip to content

Commit a24447e

Browse files
authored
Merge branch 'trunk' into tooling/integrate-automattic-encryptedlogging-1.1.0
2 parents 29658f1 + 14c2e8c commit a24447e

File tree

4 files changed

+264
-41
lines changed

4 files changed

+264
-41
lines changed

WordPress/src/main/java/org/wordpress/android/ui/reader/adapters/ReaderCommentAdapter.java

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,12 @@ public void onClick(View view) {
292292
return;
293293
}
294294

295-
final ReaderComment comment = getItem(position);
295+
int currentPosition = holder.getBindingAdapterPosition();
296+
if (currentPosition == RecyclerView.NO_POSITION) {
297+
return;
298+
}
299+
300+
ReaderComment comment = getItem(currentPosition);
296301
if (comment == null) {
297302
return;
298303
}
@@ -311,26 +316,26 @@ public void onClick(View view) {
311316
String avatarUrl = WPAvatarUtils.rewriteAvatarUrl(comment.getAuthorAvatar(), mAvatarSz);
312317
mImageManager.loadIntoCircle(commentHolder.mImgAvatar, ImageType.AVATAR, avatarUrl);
313318

314-
// tapping avatar or author name opens blog preview
315-
if (comment.hasAuthorBlogId()) {
316-
View.OnClickListener authorListener = new View.OnClickListener() {
317-
@Override
318-
public void onClick(View view) {
319+
View.OnClickListener authorListener = new View.OnClickListener() {
320+
@Override
321+
public void onClick(View view) {
322+
int authorCurrentPosition = commentHolder.getBindingAdapterPosition();
323+
if (authorCurrentPosition == RecyclerView.NO_POSITION) return;
324+
325+
ReaderComment currentComment = getItem(authorCurrentPosition);
326+
// tapping avatar or author name opens blog preview
327+
if (currentComment != null && currentComment.hasAuthorBlogId()) {
319328
ReaderActivityLauncher.showReaderBlogPreview(
320329
view.getContext(),
321-
comment.authorBlogId,
330+
currentComment.authorBlogId,
322331
mPost.isFollowedByCurrentUser,
323332
ReaderTracker.SOURCE_COMMENT,
324333
mReaderTracker
325334
);
326335
}
327-
};
328-
commentHolder.mAuthorContainer.setOnClickListener(authorListener);
329-
commentHolder.mAuthorContainer.setOnClickListener(authorListener);
330-
} else {
331-
commentHolder.mAuthorContainer.setOnClickListener(null);
332-
commentHolder.mAuthorContainer.setOnClickListener(null);
333-
}
336+
}
337+
};
338+
commentHolder.mAuthorContainer.setOnClickListener(authorListener);
334339

335340
// author name uses different color for comments from the post's author
336341
if (comment.authorId == mPost.authorId) {
@@ -382,17 +387,30 @@ public void onClick(View view) {
382387
menuPopup.setAnchorView(commentHolder.mActionButton);
383388
menuPopup.setModal(true);
384389
menuPopup.setOnItemClickListener((parent, view, position1, id) -> {
385-
mCommentMenuActionListener
386-
.onCommentMenuItemTapped(comment, actions.get(position1).getType());
390+
int menuCurrentPosition = commentHolder.getBindingAdapterPosition();
391+
if (menuCurrentPosition != RecyclerView.NO_POSITION) {
392+
ReaderComment currentComment = getItem(menuCurrentPosition);
393+
if (currentComment != null) {
394+
mCommentMenuActionListener
395+
.onCommentMenuItemTapped(currentComment, actions.get(position1).getType());
396+
}
397+
}
387398
menuPopup.dismiss();
388399
});
389400
menuPopup.show();
390401
});
391402
} else {
392403
commentHolder.mActionButton.setImageResource(R.drawable.ic_share_white_24dp);
393-
commentHolder.mActionButtonContainer.setOnClickListener(
394-
v -> mCommentMenuActionListener
395-
.onCommentMenuItemTapped(comment, ReaderCommentMenuActionType.SHARE));
404+
commentHolder.mActionButtonContainer.setOnClickListener(v -> {
405+
int shareCurrentPosition = commentHolder.getBindingAdapterPosition();
406+
if (shareCurrentPosition != RecyclerView.NO_POSITION) {
407+
ReaderComment currentComment = getItem(shareCurrentPosition);
408+
if (currentComment != null) {
409+
mCommentMenuActionListener
410+
.onCommentMenuItemTapped(currentComment, ReaderCommentMenuActionType.SHARE);
411+
}
412+
}
413+
});
396414
}
397415

398416
// show indentation spacer for comments with parents and indent it based on comment level
@@ -441,8 +459,12 @@ public void onClick(View view) {
441459
commentHolder.mReplyView.setOnClickListener(new View.OnClickListener() {
442460
@Override
443461
public void onClick(View v) {
444-
if (mReplyListener != null) {
445-
mReplyListener.onRequestReply(comment.commentId);
462+
int replyCurrentPosition = commentHolder.getBindingAdapterPosition();
463+
if (replyCurrentPosition == RecyclerView.NO_POSITION) return;
464+
465+
ReaderComment currentComment = getItem(replyCurrentPosition);
466+
if (currentComment != null && mReplyListener != null) {
467+
mReplyListener.onRequestReply(currentComment.commentId);
446468
}
447469
}
448470
});
@@ -461,11 +483,11 @@ public void run() {
461483
}
462484
}
463485

464-
showLikeStatus(commentHolder, position);
486+
showLikeStatus(commentHolder);
465487

466488
// if we're nearing the end of the comments and we know more exist on the server,
467489
// fire request to load more
468-
if (mMoreCommentsExist && mDataRequestedListener != null && (position >= getItemCount() - NUM_HEADERS)) {
490+
if (mMoreCommentsExist && mDataRequestedListener != null && (currentPosition >= getItemCount() - NUM_HEADERS)) {
469491
mDataRequestedListener.onRequestData();
470492
}
471493
}
@@ -495,7 +517,12 @@ public void refreshPost() {
495517
}
496518
}
497519

498-
private void showLikeStatus(final CommentHolder holder, int position) {
520+
private void showLikeStatus(final CommentHolder holder) {
521+
int position = holder.getBindingAdapterPosition();
522+
if (position == RecyclerView.NO_POSITION) {
523+
return;
524+
}
525+
499526
ReaderComment comment = getItem(position);
500527
if (comment == null) {
501528
return;
@@ -515,8 +542,7 @@ private void showLikeStatus(final CommentHolder holder, int position) {
515542
holder.mCountLikes.setOnClickListener(new View.OnClickListener() {
516543
@Override
517544
public void onClick(View v) {
518-
int clickedPosition = holder.getBindingAdapterPosition();
519-
toggleLike(v.getContext(), holder, clickedPosition);
545+
toggleLike(v.getContext(), holder);
520546
}
521547
});
522548
}
@@ -526,11 +552,16 @@ public void onClick(View v) {
526552
}
527553
}
528554

529-
private void toggleLike(Context context, CommentHolder holder, int position) {
555+
private void toggleLike(Context context, CommentHolder holder) {
530556
if (!NetworkUtils.checkConnection(context)) {
531557
return;
532558
}
533559

560+
int position = holder.getBindingAdapterPosition();
561+
if (position == RecyclerView.NO_POSITION) {
562+
return;
563+
}
564+
534565
ReaderComment comment = getItem(position);
535566
if (comment == null) {
536567
ToastUtils.showToast(context, R.string.reader_toast_err_generic);
@@ -548,7 +579,7 @@ private void toggleLike(Context context, CommentHolder holder, int position) {
548579
ReaderComment updatedComment = ReaderCommentTable.getComment(comment.blogId, comment.postId, comment.commentId);
549580
if (updatedComment != null) {
550581
mComments.set(position - NUM_HEADERS, updatedComment);
551-
showLikeStatus(holder, position);
582+
showLikeStatus(holder);
552583
}
553584

554585
mReaderTracker.trackPost(isAskingToLike

libs/fluxc/src/main/java/org/wordpress/android/fluxc/network/rest/wpapi/media/MediaRSApiRestClient.kt

Lines changed: 103 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.wordpress.android.fluxc.network.rest.wpapi.media
22

33
import kotlinx.coroutines.CoroutineScope
4+
import kotlinx.coroutines.Job
45
import kotlinx.coroutines.launch
56
import org.wordpress.android.fluxc.Dispatcher
67
import org.wordpress.android.fluxc.generated.MediaActionBuilder
@@ -23,7 +24,9 @@ import rs.wordpress.api.kotlin.WpRequestResult
2324
import uniffi.wp_api.MediaCreateParams
2425
import uniffi.wp_api.MediaDetailsPayload
2526
import uniffi.wp_api.MediaListParams
27+
import uniffi.wp_api.MediaUpdateParams
2628
import uniffi.wp_api.MediaWithEditContext
29+
import java.util.concurrent.ConcurrentHashMap
2730
import javax.inject.Inject
2831
import javax.inject.Named
2932
import javax.inject.Singleton
@@ -39,6 +42,9 @@ class MediaRSApiRestClient @Inject constructor(
3942
private val wpApiClientProvider: WpApiClientProvider,
4043
private val fileCheckWrapper: FileCheckWrapper,
4144
) {
45+
// Map to store upload jobs keyed by media ID for cancellation
46+
private val uploadJobs = ConcurrentHashMap<Int, Job>()
47+
4248
fun fetchMediaList(site: SiteModel, number: Int, offset: Int, mimeType: MimeType.Type?) {
4349
scope.launch {
4450
val client = wpApiClientProvider.getWpApiClient(site)
@@ -230,7 +236,7 @@ class MediaRSApiRestClient @Inject constructor(
230236
return
231237
}
232238

233-
scope.launch {
239+
val job = scope.launch {
234240
val client = wpApiClientProvider.getWpApiClient(site)
235241

236242
val mediaResponse = client.request { requestBuilder ->
@@ -259,7 +265,13 @@ class MediaRSApiRestClient @Inject constructor(
259265
notifyMediaUploaded(media, mediaError)
260266
}
261267
}
268+
269+
// Clean up the job from the map after completion
270+
uploadJobs.remove(media.id)
262271
}
272+
273+
// Store the job in the map
274+
uploadJobs[media.id] = job
263275
}
264276

265277
private fun notifyMediaUploaded(media: MediaModel?, error: MediaError?) {
@@ -268,6 +280,81 @@ class MediaRSApiRestClient @Inject constructor(
268280
dispatcher.dispatch(UploadActionBuilder.newUploadedMediaAction(payload))
269281
}
270282

283+
fun cancelUpload(media: MediaModel?) {
284+
if (media == null) {
285+
appLogWrapper.e(AppLog.T.MEDIA, "Error: no media passed to cancel upload")
286+
return
287+
}
288+
289+
appLogWrapper.d(AppLog.T.MEDIA, "Attempting to cancel media upload with local ID: ${media.id}")
290+
291+
val job = uploadJobs[media.id]
292+
if (job != null) {
293+
job.cancel()
294+
uploadJobs.remove(media.id)
295+
296+
// Report the upload was successfully cancelled
297+
notifyMediaUploadCanceled(media)
298+
299+
appLogWrapper.d(AppLog.T.MEDIA, "Successfully cancelled media upload with local ID: ${media.id}")
300+
} else {
301+
appLogWrapper.w(AppLog.T.MEDIA, "No active upload found for media with local ID: ${media.id}")
302+
303+
// Still notify cancellation even if job wasn't found, to update UI state
304+
notifyMediaUploadCanceled(media)
305+
}
306+
}
307+
308+
private fun notifyMediaUploadCanceled(media: MediaModel) {
309+
val payload = ProgressPayload(media, 0f, false, true)
310+
dispatcher.dispatch(MediaActionBuilder.newCanceledMediaUploadAction(payload))
311+
}
312+
313+
fun pushMedia(site: SiteModel, media: MediaModel?) {
314+
if (media == null) {
315+
// caller may be expecting a notification
316+
val error = MediaError(MediaErrorType.NULL_MEDIA_ARG)
317+
error.logMessage = "Pushed media is null"
318+
notifyMediaPushed(site, null, error)
319+
return
320+
}
321+
322+
scope.launch {
323+
val client = wpApiClientProvider.getWpApiClient(site)
324+
325+
val mediaResponse = client.request { requestBuilder ->
326+
requestBuilder.media().update(media.mediaId, media.getMediaUpdateParams())
327+
}
328+
329+
when (mediaResponse) {
330+
is WpRequestResult.Success -> {
331+
appLogWrapper.d(AppLog.T.MEDIA, "Updated media with ID: " + media.mediaId)
332+
333+
val responseMedia: MediaModel = mediaResponse.response.data.toMediaModel(site.id).apply {
334+
id = media.id // be sure we are using the same local id when getting the remote response
335+
localSiteId = site.id
336+
}
337+
notifyMediaPushed(site, responseMedia, null)
338+
}
339+
340+
else -> {
341+
val mediaError = parseMediaError(mediaResponse)
342+
appLogWrapper.e(AppLog.T.MEDIA, "Update media failed: ${mediaError.message}")
343+
notifyMediaPushed(site, media, mediaError)
344+
}
345+
}
346+
}
347+
}
348+
349+
private fun notifyMediaPushed(
350+
site: SiteModel,
351+
media: MediaModel?,
352+
error: MediaError?
353+
) {
354+
val payload = MediaPayload(site, media, error)
355+
dispatcher.dispatch(MediaActionBuilder.newPushedMediaAction(payload))
356+
}
357+
271358
private fun List<MediaWithEditContext>.toMediaModelList(
272359
siteId: Int
273360
): List<MediaModel> = map { it.toMediaModel(siteId) }
@@ -276,14 +363,15 @@ class MediaRSApiRestClient @Inject constructor(
276363
siteId: Int
277364
): MediaModel = MediaModel(siteId, id).apply {
278365
url = this@toMediaModel.sourceUrl
366+
fileName = slug
367+
fileExtension = this@toMediaModel.mimeType
279368
guid = this@toMediaModel.link
280-
title = this@toMediaModel.title.rendered
281-
caption = this@toMediaModel.caption.rendered
282-
description = this@toMediaModel.description.rendered
369+
title = this@toMediaModel.title.raw
370+
caption = this@toMediaModel.caption.raw
371+
description = this@toMediaModel.description.raw
283372
alt = this@toMediaModel.altText
284373
postId = this@toMediaModel.postId ?: 0
285374
mimeType = this@toMediaModel.mimeType
286-
fileExtension = this@toMediaModel.mediaType.toString()
287375
uploadDate = this@toMediaModel.date
288376
authorId = this@toMediaModel.author
289377
uploadState = MediaUploadState.UPLOADED.toString()
@@ -308,6 +396,16 @@ class MediaRSApiRestClient @Inject constructor(
308396
}
309397
}
310398

399+
private fun MediaModel.getMediaUpdateParams() = MediaUpdateParams(
400+
postId = if (postId > 0) postId else null,
401+
title = title,
402+
caption = caption,
403+
description = description,
404+
altText = alt,
405+
author = if (authorId > 0) authorId else null,
406+
date = uploadDate
407+
)
408+
311409
class FileCheckWrapper @Inject constructor() {
312410
fun canReadFile(filePath: String) = MediaUtils.canReadFile(filePath)
313411
}

libs/fluxc/src/main/java/org/wordpress/android/fluxc/store/MediaStore.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -887,7 +887,9 @@ private void performPushMedia(@NonNull MediaPayload payload) {
887887
return;
888888
}
889889

890-
if (payload.site.isUsingWpComRestApi()) {
890+
if (payload.site.isUsingSelfHostedRestApi()) {
891+
mMediaRSApiRestClient.pushMedia(payload.site, payload.media);
892+
} else if (payload.site.isUsingWpComRestApi()) {
891893
mMediaRestClient.pushMedia(payload.site, payload.media);
892894
} else {
893895
mMediaXmlrpcClient.pushMedia(payload.site, payload.media);

0 commit comments

Comments
 (0)