Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mobile stories block - error handling 4.1 - move code out of EditPostActivity #13198

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 @@ -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
}
}