From e6e956c3f6d7ff35df228b6325b1dfeeee637967 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 18 Jul 2025 12:43:57 -0400 Subject: [PATCH 1/7] refactor: Remove redundant private Atomic site check Using `isWPComAtomic` suffices for the need of identifying sites managed by WPCOM. --- .../java/org/wordpress/android/ui/posts/EditPostActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt index 135861253d67..e53c3adb6143 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt @@ -2503,7 +2503,7 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor onXpostsSettingsCapability(isXpostsCapable) } - val isWpCom = site.isWPCom || siteModel.isPrivateWPComAtomic || siteModel.isWPComAtomic + val isWpCom = site.isWPCom || siteModel.isWPComAtomic val gutenbergWebViewAuthorizationData = GutenbergWebViewAuthorizationData( siteModel.url, isWpCom, From d7f19997d477395b6865ba21323e72212818e905 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 18 Jul 2025 12:48:47 -0400 Subject: [PATCH 2/7] feat: Cache GutenbergKit assets and improve configuration --- .../wordpress/android/ui/posts/EditPostActivity.kt | 10 ++++++---- .../gutenberg/GutenbergKitEditorFragment.java | 13 ++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt index e53c3adb6143..6a7840326958 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt @@ -2520,10 +2520,11 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor ) val postType = if (editPostRepository.isPage) "page" else "post" - val siteApiRoot = if (isWpCom) "https://public-api.wordpress.com/" else "" - val authToken = accountStore.accessToken - val authHeader = "Bearer $authToken" - val siteApiNamespace = arrayOf("sites/${site.siteId}", "sites/${UrlUtils.removeScheme(siteModel.url)}") + val siteURL = siteModel.url + val siteApiRoot = if (isWpCom) "https://public-api.wordpress.com/" else siteModel.wpApiRestUrl ?: "$siteURL/wp-json/" + // TODO: Use the application password for self-hosted sites + val authHeader = if (isWpCom) "Bearer ${accountStore.accessToken}" else "Basic " + val siteApiNamespace = if (isWpCom) arrayOf("sites/${site.siteId}/", "sites/${UrlUtils.removeScheme(siteURL)}/") else arrayOf() val languageString = perAppLocaleManager.getCurrentLocaleLanguageCode() val wpcomLocaleSlug = languageString.replace("_", "-").lowercase() @@ -2533,6 +2534,7 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor "postType" to postType, "postTitle" to editPostRepository.getPost()?.title, "postContent" to editPostRepository.getPost()?.content, + "siteURL" to siteURL, "siteApiRoot" to siteApiRoot, "namespaceExcludedPaths" to arrayOf("/wpcom/v2/following/recommendations", "/wpcom/v2/following/mine"), "authHeader" to authHeader, diff --git a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergKitEditorFragment.java b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergKitEditorFragment.java index 4686f4d3b79e..02bdf159f91b 100644 --- a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergKitEditorFragment.java +++ b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergKitEditorFragment.java @@ -41,6 +41,7 @@ import org.wordpress.android.util.AppLog.T; import org.wordpress.android.util.PermissionUtils; import org.wordpress.android.util.ProfilingUtils; +import org.wordpress.android.util.UrlUtils; import org.wordpress.android.util.helpers.MediaFile; import org.wordpress.android.util.helpers.MediaGallery; import org.wordpress.aztec.IHistoryListener; @@ -58,6 +59,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.CountDownLatch; import static org.wordpress.gutenberg.Media.createMediaUsingMimeType; @@ -565,6 +567,12 @@ public void startWithEditorSettings(@NonNull String editorSettings) { postId = -1; } + var siteURL = (String) mSettings.get("siteURL"); + var siteApiRoot = (String) mSettings.get("siteApiRoot"); + var siteApiNamespace = (String[]) mSettings.get("siteApiNamespace"); + var firstNamespace = siteApiNamespace != null && siteApiNamespace.length > 0 ? siteApiNamespace[0] : ""; + var editorAssetsEndpoint = siteApiRoot + "wpcom/v2/" + firstNamespace + "editor-assets"; + EditorConfiguration config = new EditorConfiguration.Builder() .setTitle((String) mSettings.get("postTitle")) .setContent((String) mSettings.get("postContent")) @@ -573,12 +581,15 @@ public void startWithEditorSettings(@NonNull String editorSettings) { .setThemeStyles((Boolean) mSettings.get("themeStyles")) .setPlugins((Boolean) mSettings.get("plugins")) .setSiteApiRoot((String) mSettings.get("siteApiRoot")) - .setSiteApiNamespace((String[]) mSettings.get("siteApiNamespace")) + .setSiteApiNamespace((String[]) siteApiNamespace) .setNamespaceExcludedPaths((String[]) mSettings.get("namespaceExcludedPaths")) .setAuthHeader((String) mSettings.get("authHeader")) .setWebViewGlobals((List) mSettings.get("webViewGlobals")) .setEditorSettings(editorSettings) .setLocale((String) mSettings.get("locale")) + .setEditorAssetsEndpoint(editorAssetsEndpoint) + .setCachedAssetHosts(Set.of("s0.wp.com", UrlUtils.getHost(siteURL))) + .setEnableAssetCaching(true) .build(); mGutenbergView.start(config); From f013498d85ca4436958e8efdf1c8b306d5b7e9e5 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 18 Jul 2025 12:49:31 -0400 Subject: [PATCH 3/7] fix: Disable WebView preloading causing GutenbergKit load failures The current preloading strategy results in the editor loading twice. The duplicative load results in hidden editor UI for the second load, as the loading sequence is not idempotent currently. We should refactor to ensure the editor UI is only displayed once after the editor fully loads. Currently, The first occurs without blog configuration and always loads the bundled editor. We should ensure preload loads with the proper configuration and the correct editor (bundled/remote). --- .../java/org/wordpress/android/ui/posts/EditPostActivity.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt index 6a7840326958..53dc0206aee9 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt @@ -798,9 +798,6 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor } private fun setupEditor() { - if (isGutenbergKitEditor) { - GutenbergWebViewPool.getPreloadedWebView(this) - } // Check whether to show the visual editor // NOTE: Migrate to 'androidx.preference.PreferenceManager' and 'androidx.preference.Preference' From dd36e2cefd8c6bf78830df23682cd9ba8a458b8d Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 18 Jul 2025 08:27:03 -0400 Subject: [PATCH 4/7] fix: Avoid unnecessary GutenbergKit starts The editor relies upon a subscription to editor settings within the FluxC store. FluxC can dispatch change events multiple times, leading to unexpected, unnecessary invocations of GutenbergKit's `start` method. This lead to odd outcomes. First, during editor setup, we dispatch an event to request the latest editor settings. FluxC broadcasts two change events: first with the cached settings, and second when the fetched settings resolve. This caused two `start` invocations when opening the editor. Second, the My Site fragment requests the latest settings as a performance optimization. When closing the editor and returning to My Site, the request resulted in a broadcast of updated settings, which attempt to start the editor while it was closing. --- .../editor/gutenberg/GutenbergKitEditorFragment.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergKitEditorFragment.java b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergKitEditorFragment.java index 02bdf159f91b..33c5350b1de6 100644 --- a/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergKitEditorFragment.java +++ b/libs/editor/src/main/java/org/wordpress/android/editor/gutenberg/GutenbergKitEditorFragment.java @@ -74,6 +74,7 @@ public class GutenbergKitEditorFragment extends EditorFragmentAbstract implement @Nullable private GutenbergView mGutenbergView; private static final String GUTENBERG_EDITOR_NAME = "gutenberg"; private static final String KEY_HTML_MODE_ENABLED = "KEY_HTML_MODE_ENABLED"; + private static final String KEY_EDITOR_STARTED = "KEY_EDITOR_STARTED"; private static final String KEY_EDITOR_DID_MOUNT = "KEY_EDITOR_DID_MOUNT"; private static final String ARG_IS_NEW_POST = "param_is_new_post"; private static final String ARG_GUTENBERG_WEB_VIEW_AUTH_DATA = "param_gutenberg_web_view_auth_data"; @@ -92,6 +93,7 @@ public class GutenbergKitEditorFragment extends EditorFragmentAbstract implement @Nullable private OpenMediaLibraryListener mOpenMediaLibraryListener = null; @Nullable private LogJsExceptionListener mOnLogJsExceptionListener = null; + private boolean mEditorStarted; private boolean mEditorDidMount; @Nullable private View mRootView; @@ -126,6 +128,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { if (savedInstanceState != null) { mHtmlModeEnabled = savedInstanceState.getBoolean(KEY_HTML_MODE_ENABLED); + mEditorStarted = savedInstanceState.getBoolean(KEY_EDITOR_STARTED); mEditorDidMount = savedInstanceState.getBoolean(KEY_EDITOR_DID_MOUNT); mFeaturedImageId = savedInstanceState.getLong(ARG_FEATURED_IMAGE_ID); } @@ -262,6 +265,7 @@ public void onAttach(Activity activity) { @Override public void onSaveInstanceState(Bundle outState) { outState.putBoolean(KEY_HTML_MODE_ENABLED, mHtmlModeEnabled); + outState.putBoolean(KEY_EDITOR_STARTED, mEditorStarted); outState.putBoolean(KEY_EDITOR_DID_MOUNT, mEditorDidMount); outState.putLong(ARG_FEATURED_IMAGE_ID, mFeaturedImageId); } @@ -510,6 +514,7 @@ public void onDestroy() { mHistoryChangeListener = null; mFeaturedImageChangeListener = null; } + mEditorStarted = false; super.onDestroy(); } @@ -558,7 +563,7 @@ public void onEditorThemeUpdated(Bundle editorTheme) { } public void startWithEditorSettings(@NonNull String editorSettings) { - if (mGutenbergView == null) { + if (mGutenbergView == null || mEditorStarted) { return; } @@ -592,6 +597,7 @@ public void startWithEditorSettings(@NonNull String editorSettings) { .setEnableAssetCaching(true) .build(); + mEditorStarted = true; mGutenbergView.start(config); } From 20288d076293459e12405338488b387d7b1b8be5 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 18 Jul 2025 13:07:41 -0400 Subject: [PATCH 5/7] build: Update GutenbergKit version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a39c0fbc8895..c319474c8e67 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,7 +72,7 @@ google-play-services-auth = '20.4.1' google-services = '4.4.3' gravatar = '2.5.0' greenrobot-eventbus = '3.3.1' -gutenberg-kit = 'v0.4.1' +gutenberg-kit = '153-33a5382fae96987625ad20436345805bec68fbe8' gutenberg-mobile = 'v1.121.0' indexos-media-for-mobile = '43a9026f0973a2f0a74fa813132f6a16f7499c3a' jackson-databind = '2.12.7.1' From fd985742b2aea0a69daada4cc02084566f1e7f4d Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 18 Jul 2025 13:39:36 -0400 Subject: [PATCH 6/7] refactor: Address lint errors --- .../android/ui/posts/EditPostActivity.kt | 37 ++++++++++++------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt index 53dc0206aee9..347cf162487a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt @@ -258,7 +258,6 @@ import org.wordpress.android.viewmodel.storage.StorageUtilsViewModel import org.wordpress.android.widgets.AppReviewManager.incrementInteractions import org.wordpress.android.widgets.WPSnackbar.Companion.make import org.wordpress.android.widgets.WPViewPager -import org.wordpress.gutenberg.GutenbergWebViewPool import org.wordpress.aztec.AztecExceptionHandler import org.wordpress.aztec.exceptions.DynamicLayoutGetBlockIndexOutOfBoundsException import org.wordpress.aztec.util.AztecLog @@ -2501,7 +2500,20 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor } val isWpCom = site.isWPCom || siteModel.isWPComAtomic - val gutenbergWebViewAuthorizationData = GutenbergWebViewAuthorizationData( + val gutenbergWebViewAuthorizationData = createGutenbergWebViewAuthorizationData(isWpCom) + val settings = createGutenbergKitSettings(isWpCom) + + return GutenbergKitEditorFragment.newInstance( + getContext(), + isNewPost, + gutenbergWebViewAuthorizationData, + jetpackFeatureRemovalPhaseHelper.shouldShowJetpackPoweredEditorFeatures(), + settings + ) + } + + private fun createGutenbergWebViewAuthorizationData(isWpCom: Boolean): GutenbergWebViewAuthorizationData { + return GutenbergWebViewAuthorizationData( siteModel.url, isWpCom, accountStore.account.userId, @@ -2515,18 +2527,23 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor userAgent.toString(), isJetpackSsoEnabled ) + } + private fun createGutenbergKitSettings(isWpCom: Boolean): MutableMap { val postType = if (editPostRepository.isPage) "page" else "post" val siteURL = siteModel.url - val siteApiRoot = if (isWpCom) "https://public-api.wordpress.com/" else siteModel.wpApiRestUrl ?: "$siteURL/wp-json/" - // TODO: Use the application password for self-hosted sites + val siteApiRoot = if (isWpCom) "https://public-api.wordpress.com/" + else siteModel.wpApiRestUrl ?: "$siteURL/wp-json/" + // Use the application password for self-hosted sites when available val authHeader = if (isWpCom) "Bearer ${accountStore.accessToken}" else "Basic " - val siteApiNamespace = if (isWpCom) arrayOf("sites/${site.siteId}/", "sites/${UrlUtils.removeScheme(siteURL)}/") else arrayOf() + val siteApiNamespace = if (isWpCom) + arrayOf("sites/${site.siteId}/", "sites/${UrlUtils.removeScheme(siteURL)}/") + else arrayOf() val languageString = perAppLocaleManager.getCurrentLocaleLanguageCode() val wpcomLocaleSlug = languageString.replace("_", "-").lowercase() - val settings = mutableMapOf( + return mutableMapOf( "postId" to editPostRepository.getPost()?.remotePostId?.toInt(), "postType" to postType, "postTitle" to editPostRepository.getPost()?.title, @@ -2551,14 +2568,6 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor ) ) ) - - return GutenbergKitEditorFragment.newInstance( - getContext(), - isNewPost, - gutenbergWebViewAuthorizationData, - jetpackFeatureRemovalPhaseHelper.shouldShowJetpackPoweredEditorFeatures(), - settings - ) } private fun createGutenbergEditorFragment(): GutenbergEditorFragment { From 3ef5c35392adc3204c92e0196c96bced412e06d3 Mon Sep 17 00:00:00 2001 From: David Calhoun Date: Fri, 18 Jul 2025 15:49:16 -0400 Subject: [PATCH 7/7] build: Update GutenbergKit version --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c319474c8e67..9112442e19fd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -72,7 +72,7 @@ google-play-services-auth = '20.4.1' google-services = '4.4.3' gravatar = '2.5.0' greenrobot-eventbus = '3.3.1' -gutenberg-kit = '153-33a5382fae96987625ad20436345805bec68fbe8' +gutenberg-kit = 'v0.5.0' gutenberg-mobile = 'v1.121.0' indexos-media-for-mobile = '43a9026f0973a2f0a74fa813132f6a16f7499c3a' jackson-databind = '2.12.7.1'