From 8f9a82091b5452ed8dc279c3fea5c270455622c8 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Mon, 19 Aug 2019 15:48:14 +0200 Subject: [PATCH 01/10] First step at checking and restoring unhandled auto save revision --- .../android/ui/posts/PostActionHandler.kt | 9 ++++++++- .../android/ui/posts/PostConflictResolver.kt | 18 ++++++++++++++++++ .../android/ui/posts/PostListMainViewModel.kt | 2 ++ .../wordpress/android/ui/posts/PostUtils.java | 11 +++++++++++ .../posts/PostListItemUiStateHelper.kt | 16 +++++++++++----- .../viewmodel/posts/PostListViewModel.kt | 1 + .../posts/PostListViewModelConnector.kt | 1 + WordPress/src/main/res/values/strings.xml | 3 ++- .../posts/PostListItemUiStateHelperTest.kt | 8 ++++++++ .../viewmodel/posts/PostListViewModelTest.kt | 1 + build.gradle | 2 +- 11 files changed, 64 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostActionHandler.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostActionHandler.kt index bf6fffeae85e..bcb180dd0cb3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostActionHandler.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostActionHandler.kt @@ -57,6 +57,7 @@ class PostActionHandler( private val postStore: PostStore, private val postListDialogHelper: PostListDialogHelper, private val doesPostHaveUnhandledConflict: (PostModel) -> Boolean, + private val hasUnhandledAutoSave: (PostModel) -> Boolean, private val triggerPostListAction: (PostListAction) -> Unit, private val triggerPostUploadAction: (PostUploadAction) -> Unit, private val invalidateList: () -> Unit, @@ -170,12 +171,18 @@ class PostActionHandler( } private fun editPostButtonAction(site: SiteModel, post: PostModel) { - // first of all, check whether this post is in Conflicted state. + // first of all, check whether this post is in Conflicted state with a more recent remote version if (doesPostHaveUnhandledConflict.invoke(post)) { postListDialogHelper.showConflictedPostResolutionDialog(post) return } + // Then check if it's in conflicted state with a remote auto-save + if (hasUnhandledAutoSave.invoke(post)) { + postListDialogHelper.showConflictedPostResolutionDialog(post) // FIXME + return + } + editPost(site, post) } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt index d2ab4b136d69..44d7d11d09b8 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt @@ -40,6 +40,20 @@ class PostConflictResolver( } } + fun updateConflictedPostWithAutosave(localPostId: Int) { + // We need network connection to load a remote post + if (!checkNetworkConnection()) { + return + } + + val post = getPostByLocalPostId.invoke(localPostId) + if (post != null) { + originalPostCopyForConflictUndo = post.clone() + dispatcher.dispatch(PostActionBuilder.newRestoreToAutoSaveRevisionAction(RemotePostPayload(post, site))) + showToast.invoke(ToastMessageHolder(R.string.toast_conflict_updating_post, Duration.SHORT)) + } + } + fun updateConflictedPostWithLocalVersion(localPostId: Int) { // We need network connection to push local version to remote if (!checkNetworkConnection()) { @@ -84,6 +98,10 @@ class PostConflictResolver( return !isFetchingConflictedPost && PostUtils.isPostInConflictWithRemote(post) } + fun hasUnhandledAutoSave(post: PostModel): Boolean { + return PostUtils.isPostInConflictWithAutoSave(post) + } + fun onPostSuccessfullyUpdated() { originalPostCopyForConflictUndo?.id?.let { val updatedPost = getPostByLocalPostId.invoke(it) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt index 7ecdd459eb02..4fdca50b1074 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt @@ -168,6 +168,7 @@ class PostListMainViewModel @Inject constructor( postStore = postStore, postListDialogHelper = postListDialogHelper, doesPostHaveUnhandledConflict = postConflictResolver::doesPostHaveUnhandledConflict, + hasUnhandledAutoSave = postConflictResolver::hasUnhandledAutoSave, triggerPostListAction = { _postListAction.postValue(it) }, triggerPostUploadAction = { _postUploadAction.postValue(it) }, invalidateList = this::invalidateAllLists, @@ -269,6 +270,7 @@ class PostListMainViewModel @Inject constructor( postActionHandler = postActionHandler, getUploadStatus = uploadStatusTracker::getUploadStatus, doesPostHaveUnhandledConflict = postConflictResolver::doesPostHaveUnhandledConflict, + doesPostHaveAutoSave = postConflictResolver::hasUnhandledAutoSave, postFetcher = postFetcher, getFeaturedImageUrl = featuredImageTracker::getFeaturedImageUrl ) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java index a7ff0318d826..f90650a527c3 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostUtils.java @@ -453,6 +453,17 @@ public static boolean isPostInConflictWithRemote(PostModel post) { return !post.getLastModified().equals(post.getRemoteLastModified()) && post.isLocallyChanged(); } + public static boolean isPostInConflictWithAutoSave(PostModel post) { + // TODO: would be great to check if title, content and excerpt are different, + // but we currently don't have them when we fetch the post list + + // Ignore auto-saves in case the post is locally changed. + // This might be changed in the future to show a better conflict UX. + return !post.isLocallyChanged() + // has auto-save + && post.hasUnpublishedRevision(); + } + public static String getConflictedPostCustomStringForDialog(PostModel post) { Context context = WordPress.getContext(); String firstPart = context.getString(R.string.dialog_confirm_load_remote_post_body); diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelper.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelper.kt index f943c001fc7e..a14283bccac4 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelper.kt @@ -69,6 +69,7 @@ class PostListItemUiStateHelper @Inject constructor(private val appPrefsWrapper: post: PostModel, uploadStatus: PostListItemUploadStatus, unhandledConflicts: Boolean, + hasAutoSave: Boolean, capabilitiesToPublish: Boolean, statsSupported: Boolean, featuredImageUrl: String?, @@ -102,14 +103,16 @@ class PostListItemUiStateHelper @Inject constructor(private val appPrefsWrapper: isLocalDraft = post.isLocalDraft, isLocallyChanged = post.isLocallyChanged, uploadUiState = uploadUiState, - hasUnhandledConflicts = unhandledConflicts + hasUnhandledConflicts = unhandledConflicts, + hasAutoSave = hasAutoSave ) val statusesColor = getStatusesColor( postStatus = postStatus, isLocalDraft = post.isLocalDraft, isLocallyChanged = post.isLocallyChanged, uploadUiState = uploadUiState, - hasUnhandledConflicts = unhandledConflicts + hasUnhandledConflicts = unhandledConflicts, + hasAutoSave = hasAutoSave ) val statusesDelimeter = UiStringRes(R.string.multiple_status_label_delimiter) val onSelected = { @@ -204,7 +207,8 @@ class PostListItemUiStateHelper @Inject constructor(private val appPrefsWrapper: isLocalDraft: Boolean, isLocallyChanged: Boolean, uploadUiState: PostUploadUiState, - hasUnhandledConflicts: Boolean + hasUnhandledConflicts: Boolean, + hasAutoSave: Boolean ): List { val labels: MutableList = ArrayList() when { @@ -233,6 +237,7 @@ class PostListItemUiStateHelper @Inject constructor(private val appPrefsWrapper: } } hasUnhandledConflicts -> labels.add(UiStringRes(R.string.local_post_is_conflicted)) + hasAutoSave -> labels.add(UiStringRes(R.string.local_post_autosave_conflict)) } // we want to show either single error/progress label or 0-n info labels. @@ -282,9 +287,10 @@ class PostListItemUiStateHelper @Inject constructor(private val appPrefsWrapper: isLocalDraft: Boolean, isLocallyChanged: Boolean, uploadUiState: PostUploadUiState, - hasUnhandledConflicts: Boolean + hasUnhandledConflicts: Boolean, + hasAutoSave: Boolean ): Int? { - val isError = uploadUiState is PostUploadUiState.UploadFailed || hasUnhandledConflicts + val isError = uploadUiState is PostUploadUiState.UploadFailed || hasUnhandledConflicts || hasAutoSave val isProgressInfo = uploadUiState is UploadingPost || uploadUiState is UploadingMedia || uploadUiState is UploadQueued val isStateInfo = isLocalDraft || isLocallyChanged || postStatus == PRIVATE || postStatus == PENDING || diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt index 75ea4c9b906f..d524754bfd3c 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt @@ -360,6 +360,7 @@ class PostListViewModel @Inject constructor( post = post, uploadStatus = connector.getUploadStatus(post, connector.site), unhandledConflicts = connector.doesPostHaveUnhandledConflict(post), + hasAutoSave = connector.doesPostHaveAutoSave(post), capabilitiesToPublish = uploadUtilsWrapper.userCanPublish(connector.site), statsSupported = isStatsSupported, featuredImageUrl = diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModelConnector.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModelConnector.kt index 18d68dc62ccd..c58e2ab0b80a 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModelConnector.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModelConnector.kt @@ -11,6 +11,7 @@ class PostListViewModelConnector( val postActionHandler: PostActionHandler, val getUploadStatus: (PostModel, SiteModel) -> PostListItemUploadStatus, val doesPostHaveUnhandledConflict: (PostModel) -> Boolean, + val doesPostHaveAutoSave: (PostModel) -> Boolean, val postFetcher: PostFetcher, private val getFeaturedImageUrl: (site: SiteModel, featuredImageId: Long) -> String? ) { diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index bec246e1007c..f56410ec9cb9 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -1405,6 +1405,7 @@ Version conflict + Unhandled Auto Save Saving… @@ -1414,7 +1415,7 @@ Can\'t preview an empty post Can\'t preview an empty page Can\'t preview an empty draft - + Links are disabled on the preview screen diff --git a/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelperTest.kt b/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelperTest.kt index fb449d105ffa..fe6ab39cc420 100644 --- a/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelperTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelperTest.kt @@ -440,6 +440,12 @@ class PostListItemUiStateHelperTest { assertThat(state.data.statuses).contains(UiStringRes(R.string.local_post_is_conflicted)) } + @Test + fun `unhandled auto-save label shown for posts with existing auto-save`() { + val state = createPostListItemUiState(hasAutoSave = true) + assertThat(state.data.statuses).contains(UiStringRes(R.string.local_post_autosave_conflict)) + } + @Test fun `uploading post label shown when the post is being uploaded`() { val state = createPostListItemUiState(uploadStatus = createUploadStatus(isUploading = true)) @@ -750,6 +756,7 @@ class PostListItemUiStateHelperTest { post: PostModel = PostModel(), uploadStatus: PostListItemUploadStatus = createUploadStatus(), unhandledConflicts: Boolean = false, + hasAutoSave: Boolean = false, capabilitiesToPublish: Boolean = true, statsSupported: Boolean = true, featuredImageUrl: String? = null, @@ -761,6 +768,7 @@ class PostListItemUiStateHelperTest { post = post, uploadStatus = uploadStatus, unhandledConflicts = unhandledConflicts, + hasAutoSave = hasAutoSave, capabilitiesToPublish = capabilitiesToPublish, statsSupported = statsSupported, featuredImageUrl = featuredImageUrl, diff --git a/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListViewModelTest.kt index 353491d4605a..af274fe7cdc1 100644 --- a/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListViewModelTest.kt @@ -109,6 +109,7 @@ class PostListViewModelTest : BaseUnitTest() { postActionHandler = mock(), getUploadStatus = mock(), doesPostHaveUnhandledConflict = mock(), + doesPostHaveAutoSave = mock(), getFeaturedImageUrl = mock(), postFetcher = mock() ) diff --git a/build.gradle b/build.gradle index 31a1402cea17..d592d7d03e84 100644 --- a/build.gradle +++ b/build.gradle @@ -106,5 +106,5 @@ buildScan { ext { daggerVersion = '2.22.1' - fluxCVersion = '969a8c3059e70e365837b9812b35b183a8e4bd42' + fluxCVersion = '5e98bf0ef2500014abae9160d45223d717f6327f' } From 27489cf1a114916ee7ab56f06199172906686077 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Tue, 20 Aug 2019 11:46:07 +0200 Subject: [PATCH 02/10] New dialog to restore/don't restore auto saves --- .../android/ui/posts/PostActionHandler.kt | 2 +- .../android/ui/posts/PostConflictResolver.kt | 2 +- .../android/ui/posts/PostListDialogHelper.kt | 24 ++++++++++++++++++- .../android/ui/posts/PostListMainViewModel.kt | 5 ++-- .../viewmodel/posts/PostListViewModel.kt | 2 +- .../posts/PostListViewModelConnector.kt | 2 +- WordPress/src/main/res/values/strings.xml | 6 +++++ .../viewmodel/posts/PostListViewModelTest.kt | 2 +- 8 files changed, 37 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostActionHandler.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostActionHandler.kt index bcb180dd0cb3..506dd926fc5c 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostActionHandler.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostActionHandler.kt @@ -179,7 +179,7 @@ class PostActionHandler( // Then check if it's in conflicted state with a remote auto-save if (hasUnhandledAutoSave.invoke(post)) { - postListDialogHelper.showConflictedPostResolutionDialog(post) // FIXME + postListDialogHelper.showAutoSaveConflictedPostResolutionDialog(post) return } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt index 44d7d11d09b8..6f9ac7531864 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt @@ -40,7 +40,7 @@ class PostConflictResolver( } } - fun updateConflictedPostWithAutosave(localPostId: Int) { + fun updateConflictedPostWithAutoSave(localPostId: Int) { // We need network connection to load a remote post if (!checkNetworkConnection()) { return diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt index 2b097c058de9..5c40422dc463 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt @@ -10,6 +10,7 @@ private const val CONFIRM_DELETE_POST_DIALOG_TAG = "CONFIRM_DELETE_POST_DIALOG_T private const val CONFIRM_PUBLISH_POST_DIALOG_TAG = "CONFIRM_PUBLISH_POST_DIALOG_TAG" private const val CONFIRM_TRASH_POST_WITH_LOCAL_CHANGES_DIALOG_TAG = "CONFIRM_TRASH_POST_WITH_LOCAL_CHANGES_DIALOG_TAG" private const val CONFIRM_ON_CONFLICT_LOAD_REMOTE_POST_DIALOG_TAG = "CONFIRM_ON_CONFLICT_LOAD_REMOTE_POST_DIALOG_TAG" +private const val CONFIRM_ON_AUTOSAVE_CONFLICT_DIALOG_TAG = "CONFIRM_ON_AUTOSAVE_CONFLICT_DIALOG_TAG" /** * This is a temporary class to make the PostListViewModel more manageable. Please feel free to refactor it any way @@ -24,6 +25,7 @@ class PostListDialogHelper( private var localPostIdForPublishDialog: Int? = null private var localPostIdForTrashPostWithLocalChangesDialog: Int? = null private var localPostIdForConflictResolutionDialog: Int? = null + private var localPostIdForAutoSaveConflictResolutionDialog: Int? = null fun showDeletePostConfirmationDialog(post: PostModel) { // We need network connection to delete a remote post, but not a local draft @@ -84,12 +86,25 @@ class PostListDialogHelper( showDialog.invoke(dialogHolder) } + fun showAutoSaveConflictedPostResolutionDialog(post: PostModel) { + val dialogHolder = DialogHolder( + tag = CONFIRM_ON_AUTOSAVE_CONFLICT_DIALOG_TAG, + title = UiStringRes(R.string.dialog_confirm_autosave_title), + message = UiStringRes(R.string.dialog_confirm_autosave_body), + positiveButton = UiStringRes(R.string.dialog_confirm_autosave_restore_button), + negativeButton = UiStringRes(R.string.dialog_confirm_autosave_dont_restore_button) + ) + localPostIdForAutoSaveConflictResolutionDialog = post.id + showDialog.invoke(dialogHolder) + } + fun onPositiveClickedForBasicDialog( instanceTag: String, trashPostWithLocalChanges: (Int) -> Unit, deletePost: (Int) -> Unit, publishPost: (Int) -> Unit, - updateConflictedPostWithRemoteVersion: (Int) -> Unit + updateConflictedPostWithRemoteVersion: (Int) -> Unit, + updateConflictedPostWithAutoSave: (Int) -> Unit ) { when (instanceTag) { CONFIRM_DELETE_POST_DIALOG_TAG -> localPostIdForDeleteDialog?.let { @@ -109,6 +124,10 @@ class PostListDialogHelper( localPostIdForTrashPostWithLocalChangesDialog = null trashPostWithLocalChanges(it) } + CONFIRM_ON_AUTOSAVE_CONFLICT_DIALOG_TAG -> localPostIdForAutoSaveConflictResolutionDialog?.let { + localPostIdForAutoSaveConflictResolutionDialog = null + updateConflictedPostWithAutoSave(it) + } else -> throw IllegalArgumentException("Dialog's positive button click is not handled: $instanceTag") } } @@ -124,6 +143,9 @@ class PostListDialogHelper( CONFIRM_ON_CONFLICT_LOAD_REMOTE_POST_DIALOG_TAG -> localPostIdForConflictResolutionDialog?.let { updateConflictedPostWithLocalVersion(it) } + CONFIRM_ON_AUTOSAVE_CONFLICT_DIALOG_TAG -> localPostIdForAutoSaveConflictResolutionDialog?.let { + updateConflictedPostWithLocalVersion(it) // TODO + } else -> throw IllegalArgumentException("Dialog's negative button click is not handled: $instanceTag") } } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt index 4fdca50b1074..7af2c1f96456 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt @@ -270,7 +270,7 @@ class PostListMainViewModel @Inject constructor( postActionHandler = postActionHandler, getUploadStatus = uploadStatusTracker::getUploadStatus, doesPostHaveUnhandledConflict = postConflictResolver::doesPostHaveUnhandledConflict, - doesPostHaveAutoSave = postConflictResolver::hasUnhandledAutoSave, + hasAutoSave = postConflictResolver::hasUnhandledAutoSave, postFetcher = postFetcher, getFeaturedImageUrl = featuredImageTracker::getFeaturedImageUrl ) @@ -368,7 +368,8 @@ class PostListMainViewModel @Inject constructor( trashPostWithLocalChanges = postActionHandler::trashPostWithLocalChanges, deletePost = postActionHandler::deletePost, publishPost = postActionHandler::publishPost, - updateConflictedPostWithRemoteVersion = postConflictResolver::updateConflictedPostWithRemoteVersion + updateConflictedPostWithRemoteVersion = postConflictResolver::updateConflictedPostWithRemoteVersion, + updateConflictedPostWithAutoSave = postConflictResolver::updateConflictedPostWithAutoSave ) } diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt index d524754bfd3c..ddc1f0dc86b4 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModel.kt @@ -360,7 +360,7 @@ class PostListViewModel @Inject constructor( post = post, uploadStatus = connector.getUploadStatus(post, connector.site), unhandledConflicts = connector.doesPostHaveUnhandledConflict(post), - hasAutoSave = connector.doesPostHaveAutoSave(post), + hasAutoSave = connector.hasAutoSave(post), capabilitiesToPublish = uploadUtilsWrapper.userCanPublish(connector.site), statsSupported = isStatsSupported, featuredImageUrl = diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModelConnector.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModelConnector.kt index c58e2ab0b80a..682d6207ecf7 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModelConnector.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListViewModelConnector.kt @@ -11,7 +11,7 @@ class PostListViewModelConnector( val postActionHandler: PostActionHandler, val getUploadStatus: (PostModel, SiteModel) -> PostListItemUploadStatus, val doesPostHaveUnhandledConflict: (PostModel) -> Boolean, - val doesPostHaveAutoSave: (PostModel) -> Boolean, + val hasAutoSave: (PostModel) -> Boolean, val postFetcher: PostFetcher, private val getFeaturedImageUrl: (site: SiteModel, featuredImageId: Long) -> String? ) { diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index f56410ec9cb9..16b1229e127a 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -353,6 +353,12 @@ Web version discarded Undo + + More Recent Version Conflict + A more recent version exists. Restore?\n\n + Restore + Don\'t Restore + Local changes Trashing this post will discard local changes, are you sure you want to continue? diff --git a/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListViewModelTest.kt index af274fe7cdc1..ad3574ad42f3 100644 --- a/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListViewModelTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListViewModelTest.kt @@ -109,7 +109,7 @@ class PostListViewModelTest : BaseUnitTest() { postActionHandler = mock(), getUploadStatus = mock(), doesPostHaveUnhandledConflict = mock(), - doesPostHaveAutoSave = mock(), + hasAutoSave = mock(), getFeaturedImageUrl = mock(), postFetcher = mock() ) From 65455edf4b7a00e8abe308f60ecc7d0c8863a794 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Tue, 20 Aug 2019 14:15:41 +0200 Subject: [PATCH 03/10] Remove unused PostConflictResolver methods --- .../android/ui/posts/PostConflictResolver.kt | 17 ++------------ .../android/ui/posts/PostListDialogHelper.kt | 22 ++++++++++++------- 2 files changed, 16 insertions(+), 23 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt index 6f9ac7531864..c6478d19c443 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt @@ -21,7 +21,8 @@ class PostConflictResolver( private val invalidateList: () -> Unit, private val checkNetworkConnection: () -> Boolean, private val showSnackbar: (SnackbarMessageHolder) -> Unit, - private val showToast: (ToastMessageHolder) -> Unit + private val showToast: (ToastMessageHolder) -> Unit, + private val triggerPostListAction: (PostListAction) -> Unit ) { private var originalPostCopyForConflictUndo: PostModel? = null private var localPostIdForFetchingRemoteVersionOfConflictedPost: Int? = null @@ -40,20 +41,6 @@ class PostConflictResolver( } } - fun updateConflictedPostWithAutoSave(localPostId: Int) { - // We need network connection to load a remote post - if (!checkNetworkConnection()) { - return - } - - val post = getPostByLocalPostId.invoke(localPostId) - if (post != null) { - originalPostCopyForConflictUndo = post.clone() - dispatcher.dispatch(PostActionBuilder.newRestoreToAutoSaveRevisionAction(RemotePostPayload(post, site))) - showToast.invoke(ToastMessageHolder(R.string.toast_conflict_updating_post, Duration.SHORT)) - } - } - fun updateConflictedPostWithLocalVersion(localPostId: Int) { // We need network connection to push local version to remote if (!checkNetworkConnection()) { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt index 5c40422dc463..38f351f9fb7b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt @@ -104,7 +104,7 @@ class PostListDialogHelper( deletePost: (Int) -> Unit, publishPost: (Int) -> Unit, updateConflictedPostWithRemoteVersion: (Int) -> Unit, - updateConflictedPostWithAutoSave: (Int) -> Unit + editRestoredAutoSavePost: (Int) -> Unit ) { when (instanceTag) { CONFIRM_DELETE_POST_DIALOG_TAG -> localPostIdForDeleteDialog?.let { @@ -125,8 +125,9 @@ class PostListDialogHelper( trashPostWithLocalChanges(it) } CONFIRM_ON_AUTOSAVE_CONFLICT_DIALOG_TAG -> localPostIdForAutoSaveConflictResolutionDialog?.let { + // open the editor with the restored auto save localPostIdForAutoSaveConflictResolutionDialog = null - updateConflictedPostWithAutoSave(it) + editRestoredAutoSavePost(it) } else -> throw IllegalArgumentException("Dialog's positive button click is not handled: $instanceTag") } @@ -134,7 +135,8 @@ class PostListDialogHelper( fun onNegativeClickedForBasicDialog( instanceTag: String, - updateConflictedPostWithLocalVersion: (Int) -> Unit + updateConflictedPostWithLocalVersion: (Int) -> Unit, + editLocalPost: (Int) -> Unit ) { when (instanceTag) { CONFIRM_DELETE_POST_DIALOG_TAG -> localPostIdForDeleteDialog = null @@ -144,7 +146,8 @@ class PostListDialogHelper( updateConflictedPostWithLocalVersion(it) } CONFIRM_ON_AUTOSAVE_CONFLICT_DIALOG_TAG -> localPostIdForAutoSaveConflictResolutionDialog?.let { - updateConflictedPostWithLocalVersion(it) // TODO + // open the editor with the local post (don't use the auto save version) + editLocalPost(it) } else -> throw IllegalArgumentException("Dialog's negative button click is not handled: $instanceTag") } @@ -152,14 +155,17 @@ class PostListDialogHelper( fun onDismissByOutsideTouchForBasicDialog( instanceTag: String, - updateConflictedPostWithLocalVersion: (Int) -> Unit + updateConflictedPostWithLocalVersion: (Int) -> Unit, + editLocalPost: (Int) -> Unit ) { - // Cancel and outside touch dismiss works the same way for all, except for conflict resolution dialog, + // Cancel and outside touch dismiss works the same way for all, except for conflict resolution dialogs, // for which tapping outside and actively tapping the "edit local" have different meanings - if (instanceTag != CONFIRM_ON_CONFLICT_LOAD_REMOTE_POST_DIALOG_TAG) { + if (instanceTag != CONFIRM_ON_CONFLICT_LOAD_REMOTE_POST_DIALOG_TAG + && instanceTag != CONFIRM_ON_AUTOSAVE_CONFLICT_DIALOG_TAG) { onNegativeClickedForBasicDialog( instanceTag = instanceTag, - updateConflictedPostWithLocalVersion = updateConflictedPostWithLocalVersion + updateConflictedPostWithLocalVersion = updateConflictedPostWithLocalVersion, + editLocalPost = editLocalPost ) } } From ad7d320e10b3e27565800ac726cf7aafdb6c5663 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Tue, 20 Aug 2019 14:16:06 +0200 Subject: [PATCH 04/10] Update dialog strings --- WordPress/src/main/res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 16b1229e127a..1c217fcdc7db 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -355,9 +355,9 @@ More Recent Version Conflict - A more recent version exists. Restore?\n\n - Restore - Don\'t Restore + A more recent version exists. Edit current version or most recent version?\n\n + More recent version + Current version Local changes From 195ed6b2124ecaf4f0827d39e10fdf4ae52861b6 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Tue, 20 Aug 2019 14:16:34 +0200 Subject: [PATCH 05/10] Add new restore autosave and edit functions --- .../android/ui/posts/PostConflictResolver.kt | 3 +- .../android/ui/posts/PostListMainViewModel.kt | 31 +++++++++++++++++-- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt index c6478d19c443..3372fc4ecd47 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostConflictResolver.kt @@ -21,8 +21,7 @@ class PostConflictResolver( private val invalidateList: () -> Unit, private val checkNetworkConnection: () -> Boolean, private val showSnackbar: (SnackbarMessageHolder) -> Unit, - private val showToast: (ToastMessageHolder) -> Unit, - private val triggerPostListAction: (PostListAction) -> Unit + private val showToast: (ToastMessageHolder) -> Unit ) { private var originalPostCopyForConflictUndo: PostModel? = null private var localPostIdForFetchingRemoteVersionOfConflictedPost: Int? = null diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt index 7af2c1f96456..2c9ec1a51106 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListMainViewModel.kt @@ -19,6 +19,7 @@ import org.wordpress.android.analytics.AnalyticsTracker.Stat.POST_LIST_SEARCH_AC import org.wordpress.android.analytics.AnalyticsTracker.Stat.POST_LIST_TAB_CHANGED import org.wordpress.android.fluxc.Dispatcher import org.wordpress.android.fluxc.generated.ListActionBuilder +import org.wordpress.android.fluxc.generated.PostActionBuilder import org.wordpress.android.fluxc.model.LocalOrRemoteId.LocalId import org.wordpress.android.fluxc.model.SiteModel import org.wordpress.android.fluxc.model.list.PostListDescriptor @@ -360,6 +361,28 @@ class PostListMainViewModel @Inject constructor( postActionHandler.handleEditPostResult(data) } + private fun editRestoredAutoSavePost(localPostId: Int) { + val post = postStore.getPostByLocalPostId(localPostId) + if (post != null) { + post.title = post.autoSaveTitle ?: post.title + post.content = post.autoSaveContent ?: post.content + post.excerpt = post.autoSaveExcerpt ?: post.excerpt + dispatcher.dispatch(PostActionBuilder.newUpdatePostAction(post)) + _postListAction.postValue(PostListAction.EditPost(site, post)) + } else { + _snackBarMessage.value = SnackbarMessageHolder(R.string.error_post_does_not_exist) + } + } + + private fun editLocalPost(localPostId: Int) { + val post = postStore.getPostByLocalPostId(localPostId) + if (post != null) { + _postListAction.postValue(PostListAction.EditPost(site, post)) + } else { + _snackBarMessage.value = SnackbarMessageHolder(R.string.error_post_does_not_exist) + } + } + // BasicFragmentDialog Events fun onPositiveClickedForBasicDialog(instanceTag: String) { @@ -369,21 +392,23 @@ class PostListMainViewModel @Inject constructor( deletePost = postActionHandler::deletePost, publishPost = postActionHandler::publishPost, updateConflictedPostWithRemoteVersion = postConflictResolver::updateConflictedPostWithRemoteVersion, - updateConflictedPostWithAutoSave = postConflictResolver::updateConflictedPostWithAutoSave + editRestoredAutoSavePost = this::editRestoredAutoSavePost ) } fun onNegativeClickedForBasicDialog(instanceTag: String) { postListDialogHelper.onNegativeClickedForBasicDialog( instanceTag = instanceTag, - updateConflictedPostWithLocalVersion = postConflictResolver::updateConflictedPostWithLocalVersion + updateConflictedPostWithLocalVersion = postConflictResolver::updateConflictedPostWithLocalVersion, + editLocalPost = this::editLocalPost ) } fun onDismissByOutsideTouchForBasicDialog(instanceTag: String) { postListDialogHelper.onDismissByOutsideTouchForBasicDialog( instanceTag = instanceTag, - updateConflictedPostWithLocalVersion = postConflictResolver::updateConflictedPostWithLocalVersion + updateConflictedPostWithLocalVersion = postConflictResolver::updateConflictedPostWithLocalVersion, + editLocalPost = this::editLocalPost ) } From 78bb46ceeb98a5111b6a792214d76e24ba8a11e5 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Wed, 21 Aug 2019 18:48:46 +0200 Subject: [PATCH 06/10] Update with latest FluxC version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d592d7d03e84..b46bc7354937 100644 --- a/build.gradle +++ b/build.gradle @@ -106,5 +106,5 @@ buildScan { ext { daggerVersion = '2.22.1' - fluxCVersion = '5e98bf0ef2500014abae9160d45223d717f6327f' + fluxCVersion = '5fbbf510dfc9e1245eb9a56baa6d91640e9b475e' } From 2162602c80ca1418ae0de5624232de9c461bd047 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Thu, 22 Aug 2019 10:56:25 +0200 Subject: [PATCH 07/10] Fix ktlint errors --- .idea/inspectionProfiles/profiles_settings.xml | 7 ------- .../org/wordpress/android/ui/posts/PostListDialogHelper.kt | 4 ++-- .../android/viewmodel/posts/PostListItemUiStateHelper.kt | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 3b312839bf2e..000000000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - \ No newline at end of file diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt index 38f351f9fb7b..acc5384ec3ae 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/PostListDialogHelper.kt @@ -160,8 +160,8 @@ class PostListDialogHelper( ) { // Cancel and outside touch dismiss works the same way for all, except for conflict resolution dialogs, // for which tapping outside and actively tapping the "edit local" have different meanings - if (instanceTag != CONFIRM_ON_CONFLICT_LOAD_REMOTE_POST_DIALOG_TAG - && instanceTag != CONFIRM_ON_AUTOSAVE_CONFLICT_DIALOG_TAG) { + if (instanceTag != CONFIRM_ON_CONFLICT_LOAD_REMOTE_POST_DIALOG_TAG && + instanceTag != CONFIRM_ON_AUTOSAVE_CONFLICT_DIALOG_TAG) { onNegativeClickedForBasicDialog( instanceTag = instanceTag, updateConflictedPostWithLocalVersion = updateConflictedPostWithLocalVersion, diff --git a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelper.kt b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelper.kt index a14283bccac4..0ffc8839b8df 100644 --- a/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelper.kt @@ -237,7 +237,7 @@ class PostListItemUiStateHelper @Inject constructor(private val appPrefsWrapper: } } hasUnhandledConflicts -> labels.add(UiStringRes(R.string.local_post_is_conflicted)) - hasAutoSave -> labels.add(UiStringRes(R.string.local_post_autosave_conflict)) + hasAutoSave -> labels.add(UiStringRes(R.string.local_post_autosave_conflict)) } // we want to show either single error/progress label or 0-n info labels. From 063df66cea2c4d7fac72e6394651b6e1820364c3 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Thu, 22 Aug 2019 17:51:03 +0200 Subject: [PATCH 08/10] Update FluxC hash --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b46bc7354937..d3b843b935a9 100644 --- a/build.gradle +++ b/build.gradle @@ -106,5 +106,5 @@ buildScan { ext { daggerVersion = '2.22.1' - fluxCVersion = '5fbbf510dfc9e1245eb9a56baa6d91640e9b475e' + fluxCVersion = '3e1b9d7' } From 84891fe0db8d75c98cf7a3eda879a595e590c771 Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Thu, 22 Aug 2019 17:51:28 +0200 Subject: [PATCH 09/10] Update release notes with unhandled auto-save conflict --- RELEASE-NOTES.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 97493ea24962..bf7cbc8b4235 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -3,6 +3,7 @@ XX.X * Add Remote Preview support for posts and pages. * Post List: Trashed post must now be restored before edit or preview. * All changes to posts and pages will be automatically synced with the server. +* Post List: Unhandled conflict with auto saves are now detected and visible. On post opening, the app will let you choose which version you prefer. 13.2 ----- From b76d40852f28c34cb1cfdb53bd2e13be4ef0cdcc Mon Sep 17 00:00:00 2001 From: Maxime Biais Date: Thu, 22 Aug 2019 18:21:53 +0200 Subject: [PATCH 10/10] Add an simple test for the auto-save label --- .../viewmodel/posts/PostListItemUiStateHelperTest.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelperTest.kt b/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelperTest.kt index fe6ab39cc420..f1792e5ff02a 100644 --- a/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelperTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/viewmodel/posts/PostListItemUiStateHelperTest.kt @@ -22,8 +22,8 @@ import org.wordpress.android.ui.posts.AuthorFilterSelection.EVERYONE import org.wordpress.android.ui.posts.AuthorFilterSelection.ME import org.wordpress.android.ui.prefs.AppPrefsWrapper import org.wordpress.android.ui.utils.UiString.UiStringRes -import org.wordpress.android.viewmodel.posts.PostListItemAction.MoreItem import org.wordpress.android.ui.utils.UiString.UiStringText +import org.wordpress.android.viewmodel.posts.PostListItemAction.MoreItem import org.wordpress.android.viewmodel.posts.PostListItemType.PostListItemUiState import org.wordpress.android.widgets.PostListButtonType @@ -405,11 +405,18 @@ class PostListItemUiStateHelperTest { assertThat(state.data.statusesColor).isEqualTo(PROGRESS_INFO_COLOR) } + @Test fun `label has error color on version conflict`() { val state = createPostListItemUiState(unhandledConflicts = true) assertThat(state.data.statusesColor).isEqualTo(ERROR_COLOR) } + @Test + fun `label has error color on auto-save conflict`() { + val state = createPostListItemUiState(hasAutoSave = true) + assertThat(state.data.statusesColor).isEqualTo(ERROR_COLOR) + } + @Test fun `private label shown for private posts`() { val state = createPostListItemUiState(post = createPostModel(status = POST_STATE_PRIVATE))