Skip to content

Commit

Permalink
Merge pull request #13198 from wordpress-mobile/try/stories-listener-…
Browse files Browse the repository at this point in the history
…move-from-editpostactivity

Mobile stories block - error handling 4.1  - move code out of EditPostActivity
  • Loading branch information
mzorz authored Oct 22, 2020
2 parents 8395bbe + 27293f2 commit 47902d1
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@
import org.wordpress.android.ui.stories.StoryRepositoryWrapper;
import org.wordpress.android.ui.stories.prefs.StoriesPrefs;
import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase;
import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase.ReCreateStoryResult;
import org.wordpress.android.ui.uploads.PostEvents;
import org.wordpress.android.ui.uploads.UploadService;
import org.wordpress.android.ui.uploads.UploadUtils;
Expand Down Expand Up @@ -677,7 +676,7 @@ protected void onCreate(Bundle savedInstanceState) {

setupPrepublishingBottomSheetRunnable();

mStoriesEventListener.start(this.getLifecycle(), mSite);
mStoriesEventListener.start(this.getLifecycle(), mSite, mEditPostRepository);
setupPreviewUI();
}

Expand Down Expand Up @@ -3252,110 +3251,30 @@ public void onTrackableEvent(TrackableEvent event, Map<String, String> propertie
}

@Override public void onStoryComposerLoadRequested(ArrayList<Object> mediaFiles, String blockId) {
ReCreateStoryResult result = mLoadStoryFromStoriesPrefsUseCase
.loadStoryFromMemoryOrRecreateFromPrefs(mSite, mediaFiles);
if (!result.getNoSlidesLoaded()) {
// Story instance loaded or re-created! Load it onto the StoryComposer for editing now
ActivityLauncher.editStoryForResult(
this,
mSite,
new LocalId(mEditPostRepository.getId()),
result.getStoryIndex(),
result.getAllStorySlidesAreEditable(),
true,
blockId
);
} else {
// unfortunately we couldn't even load the remote media Ids indicated by the StoryBlock so we can't allow
// editing at this time :(
if (mNetworkErrorOnLastMediaFetchAttempt) {
// there was an error fetching media when we were loading the editor,
// we *may* still have a possibility, tell the user they may try refreshing the media again
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(getString(R.string.dialog_edit_story_unavailable_title));
builder.setMessage(getString(R.string.dialog_edit_story_unavailable_message));
builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> {
// try another fetchMedia request
fetchMediaList();
dialog.dismiss();
});
AlertDialog dialog = builder.create();
dialog.show();
} else {
// unrecoverable error, nothing we can do, inform the user :(.
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(getString(R.string.dialog_edit_story_unrecoverable_title));
builder.setMessage(getString(R.string.dialog_edit_story_unrecoverable_message));
builder.setPositiveButton(R.string.dialog_button_ok, (dialog, id) -> {
dialog.dismiss();
});
AlertDialog dialog = builder.create();
dialog.show();
}
boolean noSlidesLoaded = mStoriesEventListener.onRequestMediaFilesEditorLoad(
this,
new LocalId(mEditPostRepository.getId()),
mNetworkErrorOnLastMediaFetchAttempt,
mediaFiles,
blockId
);

if (mNetworkErrorOnLastMediaFetchAttempt && noSlidesLoaded) {
// try another fetchMedia request
fetchMediaList();
}
}

@Override public void onRetryUploadForMediaCollection(ArrayList<Object> mediaFiles) {
ArrayList<Integer> mediaIdsToRetry = new ArrayList<>();
for (Object mediaFile : mediaFiles) {
int localMediaId
= StringUtils.stringToInt(((HashMap<String, Object>) mediaFile).get("id").toString(), 0);
if (localMediaId != 0) {
MediaModel media = mMediaStore.getMediaWithLocalId(localMediaId);
// if we find at least one item in the mediaFiles collection passed
// for which we don't have a local MediaModel, just tell the user and bail
if (media == null) {
AppLog.e(T.MEDIA, "Can't find media with local id: " + localMediaId);
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(getString(R.string.cannot_retry_deleted_media_item_fatal));
builder.setPositiveButton(R.string.yes, (dialog, id) -> {
dialog.dismiss();
});

builder.setNegativeButton(getString(R.string.no), (dialog, id) -> dialog.dismiss());

AlertDialog dialog = builder.create();
dialog.show();

return;
}

if (media.getUrl() != null && media.getUploadState().equals(MediaUploadState.UPLOADED.toString())) {
// Note: we should actually do this when the editor fragment starts instead of waiting for user
// input.
// Notify the editor fragment upload was successful and it should replace the local url by the
// remote url.
if (mEditorMediaUploadListener != null) {
mEditorMediaUploadListener.onMediaUploadSucceeded(String.valueOf(media.getId()),
FluxCUtils.mediaFileFromMediaModel(media));
}
} else {
UploadService.cancelFinalNotification(this, mEditPostRepository.getPost());
UploadService.cancelFinalNotificationForMedia(this, mSite);
mediaIdsToRetry.add(localMediaId);
}
}
}

if (!mediaIdsToRetry.isEmpty()) {
mEditorMedia.retryFailedMediaAsync(mediaIdsToRetry);
}
AnalyticsTracker.track(Stat.EDITOR_UPLOAD_MEDIA_RETRIED);
mStoriesEventListener.onRetryUploadForMediaCollection(this, mediaFiles, mEditorMediaUploadListener);
}

@Override public void onCancelUploadForMediaCollection(ArrayList<Object> mediaFiles) {
// just cancel upload for each media
for (Object mediaFile : mediaFiles) {
int localMediaId
= StringUtils.stringToInt(((HashMap<String, Object>) mediaFile).get("id").toString(), 0);
if (localMediaId != 0) {
mEditorMedia.cancelMediaUploadAsync(localMediaId, false);
}
}
mStoriesEventListener.onCancelUploadForMediaCollection(mediaFiles);
}

@Override public void onCancelSaveForMediaCollection(ArrayList<Object> mediaFiles) {
// TODO implement cancelling save process for media collection
mStoriesEventListener.onCancelSaveForMediaCollection(mediaFiles);
}

// FluxC events
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,62 @@
package org.wordpress.android.ui.posts.editor

import android.app.Activity
import android.content.DialogInterface
import android.net.Uri
import androidx.appcompat.app.AlertDialog.Builder
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Lifecycle.State.CREATED
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveCompleted
import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveFailed
import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveProgress
import com.wordpress.stories.compose.frame.StorySaveEvents.FrameSaveStart
import com.wordpress.stories.compose.frame.StorySaveEvents.StorySaveResult
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.wordpress.android.R
import org.wordpress.android.R.string
import org.wordpress.android.analytics.AnalyticsTracker
import org.wordpress.android.analytics.AnalyticsTracker.Stat.EDITOR_UPLOAD_MEDIA_RETRIED
import org.wordpress.android.editor.EditorMediaUploadListener
import org.wordpress.android.editor.gutenberg.StorySaveMediaListener
import org.wordpress.android.fluxc.Dispatcher
import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId
import org.wordpress.android.fluxc.model.MediaModel
import org.wordpress.android.fluxc.model.MediaModel.MediaUploadState.UPLOADED
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.store.MediaStore
import org.wordpress.android.ui.ActivityLauncher
import org.wordpress.android.ui.posts.EditPostRepository
import org.wordpress.android.ui.posts.editor.media.EditorMedia
import org.wordpress.android.ui.stories.SaveStoryGutenbergBlockUseCase.Companion.TEMPORARY_ID_PREFIX
import org.wordpress.android.ui.stories.StoryRepositoryWrapper
import org.wordpress.android.ui.stories.media.StoryMediaSaveUploadBridge.StoryFrameMediaModelCreatedEvent
import org.wordpress.android.ui.stories.usecase.LoadStoryFromStoriesPrefsUseCase
import org.wordpress.android.ui.uploads.UploadService
import org.wordpress.android.util.AppLog
import org.wordpress.android.util.AppLog.T.MEDIA
import org.wordpress.android.util.EventBusWrapper
import org.wordpress.android.util.FluxCUtils
import org.wordpress.android.util.StringUtils
import org.wordpress.android.util.helpers.MediaFile
import java.util.ArrayList
import java.util.HashMap
import javax.inject.Inject

class StoriesEventListener @Inject constructor(
private val dispatcher: Dispatcher,
private val mediaStore: MediaStore,
private val eventBusWrapper: EventBusWrapper,
private val editorMedia: EditorMedia,
private val loadStoryFromStoriesPrefsUseCase: LoadStoryFromStoriesPrefsUseCase,
private val storyRepositoryWrapper: StoryRepositoryWrapper
) : LifecycleObserver {
private lateinit var lifecycle: Lifecycle
private lateinit var site: SiteModel
private lateinit var editPostRepository: EditPostRepository
private var storySaveMediaListener: StorySaveMediaListener? = null

@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
Expand All @@ -43,7 +67,7 @@ class StoriesEventListener @Inject constructor(

/**
* Handles the [Lifecycle.Event.ON_DESTROY] event to cleanup the registration for dispatcher and removing the
* observer for lifecycle.
* observer for lifecycle .
*/
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
private fun onDestroy() {
Expand All @@ -52,8 +76,9 @@ class StoriesEventListener @Inject constructor(
eventBusWrapper.unregister(this)
}

fun start(lifecycle: Lifecycle, site: SiteModel) {
fun start(lifecycle: Lifecycle, site: SiteModel, editPostRepository: EditPostRepository) {
this.site = site
this.editPostRepository = editPostRepository
this.lifecycle = lifecycle
this.lifecycle.addObserver(this)
}
Expand Down Expand Up @@ -153,4 +178,129 @@ class StoriesEventListener @Inject constructor(
storySaveMediaListener?.onStorySaveResult(localMediaId, event.isSuccess())
}
}

// Editor load / cancel events
fun onRequestMediaFilesEditorLoad(
activity: Activity,
postId: LocalId,
networkErrorOnLastMediaFetchAttempt: Boolean,
mediaFiles: ArrayList<Any>,
blockId: String
): Boolean {
val reCreateStoryResult = loadStoryFromStoriesPrefsUseCase
.loadStoryFromMemoryOrRecreateFromPrefs(site, mediaFiles)
if (!reCreateStoryResult.noSlidesLoaded) {
// Story instance loaded or re-created! Load it onto the StoryComposer for editing now
ActivityLauncher.editStoryForResult(
activity,
site,
postId,
reCreateStoryResult.storyIndex,
reCreateStoryResult.allStorySlidesAreEditable,
true,
blockId
)
} else {
// unfortunately we couldn't even load the remote media Ids indicated by the StoryBlock so we can't allow
// editing at this time :(
if (networkErrorOnLastMediaFetchAttempt) {
// there was an error fetching media when we were loading the editor,
// we *may* still have a possibility, tell the user they may try refreshing the media again
val builder: Builder = MaterialAlertDialogBuilder(
activity
)
builder.setTitle(activity.getString(R.string.dialog_edit_story_unavailable_title))
builder.setMessage(activity.getString(R.string.dialog_edit_story_unavailable_message))
builder.setPositiveButton(R.string.dialog_button_ok) { dialog, id ->
dialog.dismiss()
}
val dialog = builder.create()
dialog.show()
} else {
// unrecoverable error, nothing we can do, inform the user :(.
val builder: Builder = MaterialAlertDialogBuilder(
activity
)
builder.setTitle(activity.getString(R.string.dialog_edit_story_unrecoverable_title))
builder.setMessage(activity.getString(R.string.dialog_edit_story_unrecoverable_message))
builder.setPositiveButton(R.string.dialog_button_ok) { dialog, id -> dialog.dismiss() }
val dialog = builder.create()
dialog.show()
}
}
return reCreateStoryResult.noSlidesLoaded
}

fun onCancelUploadForMediaCollection(mediaFiles: ArrayList<Any>) {
// just cancel upload for each media
for (mediaFile in mediaFiles) {
val localMediaId = StringUtils.stringToInt(
(mediaFile as HashMap<String?, Any?>)["id"].toString(), 0
)
if (localMediaId != 0) {
editorMedia.cancelMediaUploadAsync(localMediaId, false)
}
}
}

fun onRetryUploadForMediaCollection(
activity: Activity,
mediaFiles: ArrayList<Any>,
editorMediaUploadListener: EditorMediaUploadListener?
) {
val mediaIdsToRetry = ArrayList<Int>()
for (mediaFile in mediaFiles) {
val localMediaId = StringUtils.stringToInt(
(mediaFile as HashMap<String?, Any?>)["id"].toString(), 0
)
if (localMediaId != 0) {
val media: MediaModel = mediaStore.getMediaWithLocalId(localMediaId)
// if we find at least one item in the mediaFiles collection passed
// for which we don't have a local MediaModel, just tell the user and bail
if (media == null) {
AppLog.e(
MEDIA,
"Can't find media with local id: $localMediaId"
)
val builder: Builder = MaterialAlertDialogBuilder(
activity
)
builder.setTitle(activity.getString(string.cannot_retry_deleted_media_item_fatal))
builder.setPositiveButton(string.yes) { dialog, id -> dialog.dismiss() }
builder.setNegativeButton(activity.getString(string.no),
DialogInterface.OnClickListener { dialog: DialogInterface, id: Int -> dialog.dismiss() }
)
val dialog = builder.create()
dialog.show()
return
}
if (media.url != null && media.uploadState == UPLOADED.toString()) {
// Note: we should actually do this when the editor fragment starts instead of waiting for user
// input.
// Notify the editor fragment upload was successful and it should replace the local url by the
// remote url.
editorMediaUploadListener?.onMediaUploadSucceeded(
media.id.toString(),
FluxCUtils.mediaFileFromMediaModel(media)
)
} else {
UploadService.cancelFinalNotification(
activity,
editPostRepository.getPost()
)
UploadService.cancelFinalNotificationForMedia(activity, site)
mediaIdsToRetry.add(localMediaId)
}
}
}

if (!mediaIdsToRetry.isEmpty()) {
editorMedia.retryFailedMediaAsync(mediaIdsToRetry)
}
AnalyticsTracker.track(EDITOR_UPLOAD_MEDIA_RETRIED)
}

fun onCancelSaveForMediaCollection(mediaFiles: ArrayList<Any>) {
// TODO implement cancelling save process for media collection
}
}

0 comments on commit 47902d1

Please sign in to comment.