diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java index 722084ad6ae6..3bb009af7abe 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.java @@ -875,7 +875,8 @@ private void onUploadError(MediaModel media, MediaStore.MediaError error) { } if (mEditorMediaUploadListener != null) { - mEditorMediaUploadListener.onMediaUploadFailed(localMediaId, errorMessage); + mEditorMediaUploadListener.onMediaUploadFailed(localMediaId, + EditorFragmentAbstract.getEditorMimeType(mf), errorMessage); } removeMediaFromPendingList(media); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListFragment.java index d4a94e7d6a72..24c90319acda 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostsListFragment.java @@ -605,6 +605,10 @@ public void onSaveInstanceState(Bundle outState) { @Subscribe(threadMode = ThreadMode.MAIN) public void onPostChanged(OnPostChanged event) { switch (event.causeOfChange) { + // if a Post is updated, let's refresh the whole list, because we can't really know + // from FluxC which post has changed, or when. So to make sure, we go to the source, + // which is the FkuxC PostStore. + case UPDATE_POST: case FETCH_POSTS: case FETCH_PAGES: mIsFetchingPosts = false; diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/services/MediaUploadReadyProcessor.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/services/MediaUploadReadyProcessor.java index 78675081df27..7cc77dc327c1 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/services/MediaUploadReadyProcessor.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/services/MediaUploadReadyProcessor.java @@ -18,8 +18,9 @@ public PostModel replaceMediaFileWithUrlInPost(@Nullable PostModel post, String boolean showNewEditor = AppPrefs.isVisualEditorEnabled(); if (showAztecEditor) { - AztecEditorFragment.replaceMediaFileWithUrl(WordPress.getContext(), post.getContent(), + String modifiedContents = AztecEditorFragment.replaceMediaFileWithUrl(WordPress.getContext(), post.getContent(), localMediaId, mediaFile); + post.setContent(modifiedContents); } else if (showNewEditor) { // TODO implement visual editor implementation to update image/video in post } else { diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/AztecEditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/AztecEditorFragment.java index 8c1b4438b9ee..2628e62ab006 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/AztecEditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/AztecEditorFragment.java @@ -65,7 +65,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.UUID; @@ -102,6 +101,7 @@ public class AztecEditorFragment extends EditorFragmentAbstract implements private AztecText title; private AztecText content; + private boolean mAztecReady; private SourceViewEditText source; private AztecToolbar formattingToolbar; private Html.ImageGetter imageLoader; @@ -109,7 +109,7 @@ public class AztecEditorFragment extends EditorFragmentAbstract implements private Handler invalidateOptionsHandler; private Runnable invalidateOptionsRunnable; - private Map mUploadingMedia; + private HashMap mUploadingMediaProgressMax; private Set mFailedMediaIds; private long mActionStartedAt = -1; @@ -137,7 +137,7 @@ public void onCreate(Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_aztec_editor, container, false); - mUploadingMedia = new HashMap<>(); + mUploadingMediaProgressMax = new HashMap<>(); mFailedMediaIds = new HashSet<>(); title = (AztecText) view.findViewById(R.id.title); @@ -182,6 +182,10 @@ public void run() { } }; + content.refreshText(); + + mAztecReady = true; + return view; } @@ -222,15 +226,6 @@ public void onAttach(Activity activity) { } } - @Override - public void onDetach() { - // Soft cancel (delete flag off) all media uploads currently in progress - for (String mediaId : mUploadingMedia.keySet()) { - mEditorFragmentListener.onMediaUploadCancelClicked(mediaId, false); - } - super.onDetach(); - } - @Override public void onSaveInstanceState(Bundle outState) { outState.putCharSequence(ATTR_TITLE, getTitle()); @@ -322,8 +317,14 @@ public void setTitle(CharSequence text) { @Override public void setContent(CharSequence text) { content.fromHtml(text.toString()); + updateFailedMediaList(); overlayFailedMedia(); + + updateUploadingMediaList(); + overlayProgressingMedia(); + + mAztecReady = true; } /** @@ -377,7 +378,7 @@ private void toggleHtmlMode() { mEditorFragmentListener.onTrackableEvent(TrackableEvent.HTML_BUTTON_TAPPED); // Don't switch to HTML mode if currently uploading media - if (!mUploadingMedia.isEmpty() || isActionInProgress()) { + if (!mUploadingMediaProgressMax.isEmpty() || isActionInProgress()) { ToastUtils.showToast(getActivity(), R.string.alert_action_while_uploading, ToastUtils.Duration.LONG); return; } @@ -399,22 +400,72 @@ public boolean isActionInProgress() { return System.currentTimeMillis() - mActionStartedAt < MAX_ACTION_TIME_MS; } - private void updateFailedMediaList() { - AztecText.AttributePredicate failedPredicate = new AztecText.AttributePredicate() { + private void updateUploadingMediaList() { + AztecText.AttributePredicate uploadingPredicate = getPredicateWithClass("uploading"); + + mUploadingMediaProgressMax.clear(); + + // update all items with upload progress of zero + for (Attributes attrs : content.getAllElementAttributes(uploadingPredicate)) { + String localMediaId = attrs.getValue("data-wpid"); + if (!TextUtils.isEmpty(localMediaId)) { + mUploadingMediaProgressMax.put(localMediaId, 0f); + } + } + } + + private void safeAddMediaIdToSet(Set setToAddTo, String wpId){ + if (!TextUtils.isEmpty(wpId)) { + setToAddTo.add(wpId); + } + } + + private AztecText.AttributePredicate getPredicateWithClass(final String classToUse) { + AztecText.AttributePredicate predicate = new AztecText.AttributePredicate() { @Override public boolean matches(@NonNull Attributes attrs) { AttributesWithClass attributesWithClass = new AttributesWithClass(attrs); - return attributesWithClass.hasClass(ATTR_STATUS_FAILED); + return attributesWithClass.hasClass(classToUse); } }; + return predicate; + } + + private void updateFailedMediaList() { + AztecText.AttributePredicate failedPredicate = getPredicateWithClass(ATTR_STATUS_FAILED); + mFailedMediaIds.clear(); for (Attributes attrs : content.getAllElementAttributes(failedPredicate)) { - mFailedMediaIds.add(attrs.getValue(ATTR_ID_WP)); + String localMediaId = attrs.getValue(ATTR_ID_WP); + safeAddMediaIdToSet(mFailedMediaIds, localMediaId); } } + private void overlayProgressingMedia() { + for (String localMediaId : mUploadingMediaProgressMax.keySet()) { + overlayProgressingMedia(localMediaId); + } + } + + private void overlayProgressingMedia(String localMediaId) { + // set intermediate shade overlay + ImagePredicate predicate = ImagePredicate.getLocalMediaIdPredicate(localMediaId); + content.setOverlay(predicate, 0, + new ColorDrawable(getResources().getColor(R.color.media_shade_overlay_color)), + Gravity.FILL); + + Drawable progressDrawable = getResources().getDrawable(android.R.drawable.progress_horizontal); + // set the height of the progress bar to 2 (it's in dp since the drawable will be adjusted by the span) + progressDrawable.setBounds(0, 0, 0, 4); + + content.setOverlay(predicate, 1, progressDrawable, + Gravity.FILL_HORIZONTAL | Gravity.TOP); + + content.refreshText(); + } + private void overlayFailedMedia() { for (String localMediaId : mFailedMediaIds) { Attributes attributes = content.getElementAttributes(ImagePredicate.getLocalMediaIdPredicate(localMediaId)); @@ -529,23 +580,9 @@ public void onResponse(ImageLoader.ImageContainer container, boolean isImmediate } // set intermediate shade overlay - AztecText.AttributePredicate localMediaIdPredicate = ImagePredicate.getLocalMediaIdPredicate(localMediaId); - content.setOverlay(localMediaIdPredicate, 0, - new ColorDrawable(getResources().getColor(R.color.media_shade_overlay_color)), - Gravity.FILL); - - Drawable progressDrawable = getResources().getDrawable(android.R.drawable.progress_horizontal); - // set the height of the progress bar to 2 (it's in dp since the drawable will be adjusted by the span) - progressDrawable.setBounds(0, 0, 0, 4); - - content.setOverlay(localMediaIdPredicate, 1, progressDrawable, - Gravity.FILL_HORIZONTAL | Gravity.TOP); - - content.updateElementAttributes(localMediaIdPredicate, attrs); + overlayProgressingMedia(localMediaId); - content.refreshText(); - - mUploadingMedia.put(localMediaId, MediaType.IMAGE); + mUploadingMediaProgressMax.put(localMediaId, 0f); } } } @@ -561,7 +598,7 @@ public void setUrlForVideoPressId(final String videoId, final String videoUrl, f @Override public boolean isUploadingMedia() { - return (mUploadingMedia.size() > 0); + return (mUploadingMediaProgressMax.size() > 0); } @Override @@ -577,6 +614,7 @@ public boolean matches(@NotNull Attributes attrs) { return new AttributesWithClass(attrs).hasClass(ATTR_STATUS_FAILED); } }); + mFailedMediaIds.clear(); } @Override @@ -594,27 +632,37 @@ public void setContentPlaceholder(CharSequence placeholderText) { @Override public void onMediaUploadSucceeded(final String localMediaId, final MediaFile mediaFile) { - if(!isAdded()) { + if(!isAdded() || content == null || !mAztecReady) { return; } - final MediaType mediaType = mUploadingMedia.get(localMediaId); - if (mediaType != null) { + + if (mediaFile != null) { String remoteUrl = Utils.escapeQuotes(mediaFile.getFileURL()); // we still need to refresh the screen visually, no matter whether the service already // saved the post to Db or not + MediaType mediaType = EditorFragmentAbstract.getEditorMimeType(mediaFile); if (mediaType.equals(MediaType.IMAGE)) { - AztecAttributes attrs = new AztecAttributes(); - attrs.setValue(ATTR_SRC, remoteUrl); - // clear overlay ImagePredicate predicate = ImagePredicate.getLocalMediaIdPredicate(localMediaId); + + // remove the uploading class + AttributesWithClass attributesWithClass = new AttributesWithClass( + content.getElementAttributes(predicate)); + attributesWithClass.removeClass(ATTR_STATUS_UPLOADING); + + // add then new src property with the remoteUrl + AztecAttributes attrs = attributesWithClass.getAttributes(); + attrs.setValue("src", remoteUrl); + + // clear overlay content.clearOverlays(predicate); content.updateElementAttributes(predicate, attrs); content.refreshText(); - mUploadingMedia.remove(localMediaId); + + mUploadingMediaProgressMax.remove(localMediaId); } else if (mediaType.equals(MediaType.VIDEO)) { // TODO: update video element } @@ -642,23 +690,53 @@ public boolean matches(@NotNull Attributes attrs) { @Override public void onMediaUploadProgress(final String localMediaId, final float progress) { - if(!isAdded()) { + if(!isAdded() || content == null || !mAztecReady || TextUtils.isEmpty(localMediaId)) { return; } - final MediaType mediaType = mUploadingMedia.get(localMediaId); - if (mediaType != null) { - AztecText.AttributePredicate localMediaIdPredicate = ImagePredicate.getLocalMediaIdPredicate(localMediaId); - content.setOverlayLevel(localMediaIdPredicate, 1, (int)(progress * 10000)); - content.refreshText(); + + // check a previous maximum for this localMediaId exists + // if there is not, we've probably already gotten the upload fail/success signal, thus + // we already removed this id from the array. Nothing left to do, disregard this event. + if (mUploadingMediaProgressMax.get(localMediaId) == null) { + return; + } + + // first obtain the latest maximum + float maxProgressForLocalMediaId = mUploadingMediaProgressMax.get(localMediaId); + + // only update if the new progress value is greater than the latest maximum reflected on the + // screen + if (progress > maxProgressForLocalMediaId) { + + synchronized (AztecEditorFragment.this) { + maxProgressForLocalMediaId = progress; + mUploadingMediaProgressMax.put(localMediaId, maxProgressForLocalMediaId); + + try { + AztecText.AttributePredicate localMediaIdPredicate = ImagePredicate.getLocalMediaIdPredicate(localMediaId); + content.setOverlayLevel(localMediaIdPredicate, 1, (int) (progress * 10000)); + content.refreshText(); + } catch (IndexOutOfBoundsException ex) { + /* + * it could happen that the localMediaId is not found, because FluxC events are not + * guaranteed to be received in order, so we might have received the `upload + * finished` event (thus clearing the id from within the Post html), and then + * still receive some more progress events for the same file, which we can't + * avoid but disregard. + * ex.printStackTrace(); + */ + AppLog.d(AppLog.T.EDITOR, localMediaId + " - not found trying to update progress "); + } + } } } @Override - public void onMediaUploadFailed(final String localMediaId, final String errorMessage) { - if(!isAdded()) { + public void onMediaUploadFailed(final String localMediaId, final EditorFragmentAbstract.MediaType + mediaType, final String errorMessage) { + if(!isAdded() || content == null) { return; } - MediaType mediaType = mUploadingMedia.get(localMediaId); if (mediaType != null) { switch (mediaType) { case IMAGE: @@ -675,7 +753,7 @@ public void onMediaUploadFailed(final String localMediaId, final String errorMes // TODO: mark media as upload-failed } mFailedMediaIds.add(localMediaId); - mUploadingMedia.remove(localMediaId); + mUploadingMediaProgressMax.remove(localMediaId); } } @@ -915,7 +993,7 @@ public void onMediaTapped(final String localMediaId, final MediaType mediaType, builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { - if (mUploadingMedia.containsKey(localMediaId)) { + if (mUploadingMediaProgressMax.containsKey(localMediaId)) { mEditorFragmentListener.onMediaUploadCancelClicked(localMediaId, true); switch (mediaType) { @@ -925,7 +1003,7 @@ public void onClick(DialogInterface dialog, int id) { case VIDEO: // TODO: remove video } - mUploadingMedia.remove(localMediaId); + mUploadingMediaProgressMax.remove(localMediaId); } else { ToastUtils.showToast(getActivity(), R.string.upload_finished_toast).show(); } @@ -971,7 +1049,7 @@ public void onClick(DialogInterface dialog, int id) { // TODO: unmark video failed } mFailedMediaIds.remove(localMediaId); - mUploadingMedia.put(localMediaId, mediaType); + mUploadingMediaProgressMax.put(localMediaId, 0f); break; default: if (!mediaType.equals(MediaType.IMAGE)) { @@ -1137,13 +1215,23 @@ public static String replaceMediaFileWithUrl(Context context, @NonNull String po if (mediaFile != null) { String remoteUrl = Utils.escapeQuotes(mediaFile.getFileURL()); if (!mediaFile.isVideo()) { - AztecAttributes attrs = new AztecAttributes(); - attrs.setValue("src", remoteUrl); - // clear overlay + // fill in Aztec with the post's content AztecText content = new AztecText(context); content.fromHtml(postContent); + ImagePredicate predicate = ImagePredicate.getLocalMediaIdPredicate(localMediaId); + + // remove then uploading class + AttributesWithClass attributesWithClass = new AttributesWithClass( + content.getElementAttributes(predicate)); + attributesWithClass.removeClass(ATTR_STATUS_UPLOADING); + + // add then new src property with the remoteUrl + AztecAttributes attrs = attributesWithClass.getAttributes(); + attrs.setValue("src", remoteUrl); + + // clear overlay content.clearOverlays(predicate); content.updateElementAttributes(predicate, attrs); content.refreshText(); diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java index b14333d397b1..33e52e0c7ad6 100755 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragment.java @@ -399,15 +399,6 @@ public void onAttach(Activity activity) { } } - @Override - public void onDetach() { - // Soft cancel (delete flag off) all media uploads currently in progress - for (String mediaId : mUploadingMedia.keySet()) { - mEditorFragmentListener.onMediaUploadCancelClicked(mediaId, false); - } - super.onDetach(); - } - @Override public void setUserVisibleHint(boolean isVisibleToUser) { if (mDomHasLoaded) { @@ -1144,21 +1135,19 @@ public void onMediaUploadProgress(final String mediaId, final float progress) { return; } - final MediaType mediaType = mUploadingMedia.get(mediaId); - if (mediaType != null) { - mWebView.post(new Runnable() { - @Override - public void run() { - String progressString = String.format(Locale.US, "%.1f", progress); - mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnMedia(" + mediaId + ", " + - progressString + ");"); - } - }); - } + mWebView.post(new Runnable() { + @Override + public void run() { + String progressString = String.format(Locale.US, "%.1f", progress); + mWebView.execJavaScriptFromString("ZSSEditor.setProgressOnMedia(" + mediaId + ", " + + progressString + ");"); + } + }); } @Override - public void onMediaUploadFailed(final String mediaId, final String errorMessage) { + public void onMediaUploadFailed(final String mediaId, final EditorFragmentAbstract.MediaType + mediaType, final String errorMessage) { if(!isAdded()) { return; } diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java index 4c08529a3aed..9c0c8e02f123 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorFragmentAbstract.java @@ -157,6 +157,15 @@ public void setLocalDraft(boolean isLocalDraft) { // Not unused in the new editor } + public static MediaType getEditorMimeType(MediaFile mediaFile) { + if (mediaFile == null) { + // default to image + return MediaType.IMAGE; + } + return mediaFile.isVideo() ? MediaType.VIDEO : + MediaType.IMAGE; + } + /** * Callbacks used to communicate with the parent Activity */ diff --git a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java index 389fb31fe365..d1be3a5ed4cc 100644 --- a/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java +++ b/libs/editor/WordPressEditor/src/main/java/org/wordpress/android/editor/EditorMediaUploadListener.java @@ -5,6 +5,6 @@ public interface EditorMediaUploadListener { void onMediaUploadSucceeded(String localId, MediaFile mediaFile); void onMediaUploadProgress(String localId, float progress); - void onMediaUploadFailed(String localId, String errorMessage); + void onMediaUploadFailed(String localId, EditorFragmentAbstract.MediaType mediaType, String errorMessage); void onGalleryMediaUploadSucceeded(long galleryId, long remoteId, int remaining); } diff --git a/libs/editor/example/src/main/java/org/wordpress/android/editor/example/EditorExampleActivity.java b/libs/editor/example/src/main/java/org/wordpress/android/editor/example/EditorExampleActivity.java index 74c1702a2d42..80f7b7c14697 100644 --- a/libs/editor/example/src/main/java/org/wordpress/android/editor/example/EditorExampleActivity.java +++ b/libs/editor/example/src/main/java/org/wordpress/android/editor/example/EditorExampleActivity.java @@ -289,7 +289,7 @@ public void run() { count += 0.1; } - ((EditorMediaUploadListener) mEditorFragment).onMediaUploadFailed(mediaId, + ((EditorMediaUploadListener) mEditorFragment).onMediaUploadFailed(mediaId, EditorFragmentAbstract.MediaType.IMAGE, getString(R.string.tap_to_try_again)); mFailedUploads.put(mediaId, mediaUrl);