Skip to content

Commit

Permalink
Merge pull request #20301 from wordpress-mobile/issue/20189-post-sync…
Browse files Browse the repository at this point in the history
…-before-editing

[Offline Mode] Post sync before editing
  • Loading branch information
pantstamp authored Mar 7, 2024
2 parents 0cdfeac + 9abd2ca commit e93b549
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 5 deletions.
1 change: 1 addition & 0 deletions WordPress/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ android {
buildConfigField "boolean", "BLAZE_MANAGE_CAMPAIGNS", "false"
buildConfigField "boolean", "DASHBOARD_PERSONALIZATION", "false"
buildConfigField "boolean", "ENABLE_SITE_MONITORING", "false"
buildConfigField "boolean", "SYNC_PUBLISHING", "false"

manifestPlaceholders = [magicLinkScheme:"wordpress"]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.wordpress.android.modules

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import org.wordpress.android.ui.posts.IPostFreshnessChecker
import org.wordpress.android.ui.posts.PostFreshnessCheckerImpl
import javax.inject.Singleton

@InstallIn(SingletonComponent::class)
@Module
class PostModule {
@Singleton
@Provides
fun providePostFreshnessChecker(): IPostFreshnessChecker = PostFreshnessCheckerImpl()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.webkit.MimeTypeMap;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.Toast;

Expand Down Expand Up @@ -221,6 +222,7 @@
import org.wordpress.android.util.analytics.AnalyticsUtils.BlockEditorEnabledSource;
import org.wordpress.android.util.config.ContactSupportFeatureConfig;
import org.wordpress.android.util.config.GlobalStyleSupportFeatureConfig;
import org.wordpress.android.util.config.SyncPublishingFeatureConfig;
import org.wordpress.android.util.extensions.AppBarLayoutExtensionsKt;
import org.wordpress.android.util.helpers.MediaFile;
import org.wordpress.android.util.helpers.MediaGallery;
Expand Down Expand Up @@ -382,6 +384,8 @@ enum RestartEditorOptions {

private boolean mHtmlModeMenuStateOn = false;

private FrameLayout mUpdatingPostArea;

@Inject Dispatcher mDispatcher;
@Inject AccountStore mAccountStore;
@Inject SiteStore mSiteStore;
Expand Down Expand Up @@ -423,6 +427,7 @@ enum RestartEditorOptions {
@Inject BloggingPromptsStore mBloggingPromptsStore;
@Inject JetpackFeatureRemovalPhaseHelper mJetpackFeatureRemovalPhaseHelper;
@Inject ContactSupportFeatureConfig mContactSupportFeatureConfig;
@Inject SyncPublishingFeatureConfig mSyncPublishingFeatureConfig;

private StorePostViewModel mViewModel;
private StorageUtilsViewModel mStorageUtilsViewModel;
Expand All @@ -437,6 +442,11 @@ enum RestartEditorOptions {

private ActivityResultLauncher<Intent> mEditShareMessageActivityResultLauncher;

private final Handler mHideUpdatingPostAreaHandler = new Handler(Looper.getMainLooper());
private Runnable mHideUpdatingPostAreaRunnable;
private long mUpdatingPostStartTime = 0L;
private static final long MIN_UPDATING_POST_DISPLAY_TIME = 2000L; // Minimum display time in milliseconds

public static boolean checkToRestart(@NonNull Intent data) {
return data.hasExtra(EXTRA_RESTART_EDITOR)
&& RestartEditorOptions.valueOf(data.getStringExtra(EXTRA_RESTART_EDITOR))
Expand Down Expand Up @@ -558,12 +568,15 @@ public void handleOnBackPressed() {
getOnBackPressedDispatcher().addCallback(this, callback);

mDispatcher.register(this);

// initialise ViewModels
mViewModel = new ViewModelProvider(this, mViewModelFactory).get(StorePostViewModel.class);
mStorageUtilsViewModel = new ViewModelProvider(this, mViewModelFactory).get(StorageUtilsViewModel.class);
mEditorBloggingPromptsViewModel = new ViewModelProvider(this, mViewModelFactory)
.get(EditorBloggingPromptsViewModel.class);
mEditorJetpackSocialViewModel = new ViewModelProvider(this, mViewModelFactory)
.get(EditorJetpackSocialViewModel.class);

setContentView(R.layout.new_edit_post_activity);

createEditShareMessageActivityResultLauncher();
Expand Down Expand Up @@ -615,7 +628,6 @@ public void handleOnBackPressed() {
mToolbar = findViewById(R.id.toolbar_main);
setSupportActionBar(mToolbar);


final ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(false);
Expand Down Expand Up @@ -797,6 +809,46 @@ public void handleOnBackPressed() {
mEditorJetpackSocialViewModel.start(mSite, mEditPostRepository);

customizeToolbar();

mUpdatingPostArea = findViewById(R.id.updating);

// check if post content needs updating
if (mSyncPublishingFeatureConfig.isEnabled()) {
mViewModel.checkIfUpdatedPostVersionExists(mEditPostRepository, mSite);
}
}
private void showUpdatingPostArea() {
mUpdatingPostArea.setVisibility(View.VISIBLE);
mUpdatingPostStartTime = System.currentTimeMillis();
// Cancel any pending hide operations to avoid conflicts
if (mHideUpdatingPostAreaRunnable != null) {
mHideUpdatingPostAreaHandler.removeCallbacks(mHideUpdatingPostAreaRunnable);
}
}

private void hideUpdatingPostArea() {
long elapsedTime = System.currentTimeMillis() - mUpdatingPostStartTime;
long delay = MIN_UPDATING_POST_DISPLAY_TIME - elapsedTime;

if (delay > 0) {
// Delay hiding the view if the elapsed time is less than the minimum display time
hideUpdatingPostAreaWithDelay(delay);
} else {
// Hide the view immediately if the minimum display time has been met or exceeded
mUpdatingPostArea.setVisibility(View.GONE);
}
}

private void hideUpdatingPostAreaWithDelay(long delay) {
// Define the runnable only once or ensure it's the same instance if it's already defined
if (mHideUpdatingPostAreaRunnable == null) {
mHideUpdatingPostAreaRunnable = () -> {
if (mUpdatingPostArea != null) {
mUpdatingPostArea.setVisibility(View.GONE);
}
};
}
mHideUpdatingPostAreaHandler.postDelayed(mHideUpdatingPostAreaRunnable, delay);
}

private void customizeToolbar() {
Expand Down Expand Up @@ -1013,6 +1065,27 @@ private void startObserving() {
);
}
});

mViewModel.getOnPostUpdateUiVisible().observe(this, isVisible -> {
if (isVisible) {
showUpdatingPostArea();
} else {
hideUpdatingPostArea();
}
});

mViewModel.getOnPostUpdateResult().observe(this, isSuccess -> {
if (isSuccess) {
mEditPostRepository.loadPostByLocalPostId(mEditPostRepository.getId());
refreshEditorContent();
} else {
ToastUtils.showToast(
EditPostActivity.this,
getString(R.string.editor_updating_post_failed),
ToastUtils.Duration.SHORT
);
}
});
}

private void initializePostObject() {
Expand Down Expand Up @@ -1094,6 +1167,10 @@ protected void onPause() {
if (mShowPrepublishingBottomSheetHandler != null && mShowPrepublishingBottomSheetRunnable != null) {
mShowPrepublishingBottomSheetHandler.removeCallbacks(mShowPrepublishingBottomSheetRunnable);
}

if (mHideUpdatingPostAreaHandler != null && mHideUpdatingPostAreaRunnable != null) {
mHideUpdatingPostAreaHandler.removeCallbacks(mHideUpdatingPostAreaRunnable);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.wordpress.android.ui.posts

import org.wordpress.android.fluxc.model.PostImmutableModel

/**
* This interface is implemented by a component that determines if a post
* is "fresh" or we need to refetch it from the backend.
*/
interface IPostFreshnessChecker {
fun shouldRefreshPost(post: PostImmutableModel): Boolean
}

interface TimeProvider {
fun currentTimeMillis(): Long
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.wordpress.android.ui.posts

import org.wordpress.android.fluxc.model.PostImmutableModel

class PostFreshnessCheckerImpl(
private val timeProvider: TimeProvider = SystemTimeProvider()
) : IPostFreshnessChecker {
override fun shouldRefreshPost(post: PostImmutableModel): Boolean {
return postNeedsRefresh(post)
}

private fun postNeedsRefresh(post: PostImmutableModel) : Boolean {
return timeProvider.currentTimeMillis() - post.dbTimestamp > CACHE_VALIDITY_MILLIS
}

companion object {
// Todo turn this into a remote config value
const val CACHE_VALIDITY_MILLIS = 20000
}
}

class SystemTimeProvider : TimeProvider {
override fun currentTimeMillis(): Long = System.currentTimeMillis()
}

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import javax.inject.Inject
*
*/
@Reusable
class PostUtilsWrapper @Inject constructor(private val dateProvider: DateProvider) {
class PostUtilsWrapper
@Inject constructor(
private val dateProvider: DateProvider
) {
fun isPublishable(post: PostImmutableModel) = PostUtils.isPublishable(post)

fun isPostInConflictWithRemote(post: PostImmutableModel) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@ import org.wordpress.android.editor.gutenberg.DialogVisibility.Hidden
import org.wordpress.android.editor.gutenberg.DialogVisibility.Showing
import org.wordpress.android.editor.gutenberg.DialogVisibilityProvider
import org.wordpress.android.fluxc.Dispatcher
import org.wordpress.android.fluxc.generated.PostActionBuilder
import org.wordpress.android.fluxc.model.CauseOfOnPostChanged
import org.wordpress.android.fluxc.model.PostImmutableModel
import org.wordpress.android.fluxc.model.PostModel
import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.fluxc.store.PostStore.OnPostChanged
import org.wordpress.android.fluxc.store.PostStore.OnPostUploaded
import org.wordpress.android.fluxc.store.PostStore.RemotePostPayload
import org.wordpress.android.fluxc.store.SiteStore
import org.wordpress.android.modules.UI_THREAD
import org.wordpress.android.ui.posts.EditPostRepository
import org.wordpress.android.ui.posts.EditPostRepository.UpdatePostResult
import org.wordpress.android.ui.posts.IPostFreshnessChecker
import org.wordpress.android.ui.posts.PostUtilsWrapper
import org.wordpress.android.ui.posts.SavePostToDbUseCase
import org.wordpress.android.ui.posts.editor.StorePostViewModel.ActivityFinishState.SAVED_LOCALLY
Expand All @@ -46,7 +50,8 @@ class StorePostViewModel
private val uploadService: UploadServiceFacade,
private val savePostToDbUseCase: SavePostToDbUseCase,
private val networkUtils: NetworkUtilsWrapper,
private val dispatcher: Dispatcher
private val dispatcher: Dispatcher,
private val postFreshnessChecker: IPostFreshnessChecker
) : ScopedViewModel(uiCoroutineDispatcher), DialogVisibilityProvider {
private var debounceCounter = 0
private var saveJob: Job? = null
Expand All @@ -64,6 +69,12 @@ class StorePostViewModel
}
override val savingInProgressDialogVisibility: LiveData<DialogVisibility> = _savingProgressDialogVisibility

private val _onPostUpdateUiVisible = MutableLiveData<Boolean>()
val onPostUpdateUiVisible: LiveData<Boolean> = _onPostUpdateUiVisible

private val _onPostUpdateResult = MutableLiveData<Boolean>()
val onPostUpdateResult: LiveData<Boolean> = _onPostUpdateResult

init {
dispatcher.register(this)
}
Expand Down Expand Up @@ -190,6 +201,21 @@ class StorePostViewModel
_onFinish.postValue(Event(state))
}

fun checkIfUpdatedPostVersionExists(
editPostRepository: EditPostRepository,
site: SiteModel
) {
editPostRepository.getPost()?.let { postModel ->
if (!postModel.isLocalDraft
&& !postModel.isLocallyChanged
&& postFreshnessChecker.shouldRefreshPost(postModel)) {
_onPostUpdateUiVisible.postValue(true)
val payload = RemotePostPayload(editPostRepository.getEditablePost(), site)
dispatcher.dispatch(PostActionBuilder.newFetchPostAction(payload))
}
}
}

@Suppress("unused", "UNUSED_PARAMETER")
@Subscribe
fun onPostUploaded(event: OnPostUploaded) {
Expand All @@ -200,6 +226,17 @@ class StorePostViewModel
@Subscribe
fun onPostChanged(event: OnPostChanged) {
hideSavingProgressDialog()

// Refresh post content if needed
(event.causeOfChange as? CauseOfOnPostChanged.UpdatePost)?.let { updatePost ->
// if post update is only local do nothing
if (!updatePost.isLocalUpdate) {
// Post the result based on `event.isError`
_onPostUpdateResult.postValue(!event.isError)
// Hide updating post area
_onPostUpdateUiVisible.postValue(false)
}
}
}

sealed class UpdateResult {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.wordpress.android.util.config

import org.wordpress.android.BuildConfig
import org.wordpress.android.annotation.Feature
import javax.inject.Inject

private const val SYNC_PUBLISHING_FEATURE_REMOTE_FIELD = "sync_publishing"

@Feature(SYNC_PUBLISHING_FEATURE_REMOTE_FIELD, false)
class SyncPublishingFeatureConfig @Inject constructor(
appConfig: AppConfig
) : FeatureConfig(
appConfig,
BuildConfig.SYNC_PUBLISHING,
SYNC_PUBLISHING_FEATURE_REMOTE_FIELD
)
38 changes: 38 additions & 0 deletions WordPress/src/main/res/layout/new_edit_post_activity.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,44 @@
tools:context=".ui.photopicker.PhotoPickerFragment"
tools:visibility="visible" />

<FrameLayout
android:id="@+id/updating"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:background="@color/post_editing_updating"
android:visibility="gone">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
android:gravity="center"
android:padding="8dp"
android:background="@color/post_editing_updating">

<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/editor_updating_post"
android:layout_marginBottom="8dp"
android:textColor="@color/black"
android:textAppearance="?attr/textAppearanceBody1"/>

<ProgressBar
style="?android:attr/progressBarStyle"
android:layout_width="30dp"
android:layout_height="30dp"
android:indeterminateTint="@color/black"
/>

</LinearLayout>

</FrameLayout>

</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
4 changes: 4 additions & 0 deletions WordPress/src/main/res/values/colors.xml
Original file line number Diff line number Diff line change
Expand Up @@ -158,4 +158,8 @@
<!-- Gravatar Info Banners -->
<color name="gravatar_info_banner">#F2F2F7</color>
<color name="gravatar_sync_info_banner">#2C2C2E</color>

<!-- Post Editing -->
<color name="post_editing_updating">#EDD6C5</color>

</resources>
2 changes: 2 additions & 0 deletions WordPress/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1712,6 +1712,8 @@
<string name="editor_uploading_draft">Your draft is uploading</string>
<string name="editor_post_converted_back_to_draft">Post converted back to draft</string>
<string name="editor_failed_to_insert_media_tap_to_retry">Failed to insert media.\nPlease tap to retry.</string>
<string name="editor_updating_post">Updating post content</string>
<string name="editor_updating_post_failed">Failed to update post content</string>

<!-- Post Settings -->
<string name="post_settings">Post settings</string>
Expand Down
Loading

0 comments on commit e93b549

Please sign in to comment.