Skip to content

Commit 37a5943

Browse files
authored
EditorLauncher Scaffold Implementation (#22111)
* Add EditorLauncher scaffold with centralized editor routing Implement hybrid approach for editor activity routing that will support both legacy EditPostActivity and future EditPostGutenbergKitActivity. Changes: - Add EditorLauncher singleton with feature-flag-based routing logic - Migrate all ActivityLauncher methods to use EditorLauncher helper - Add EditorLauncher to dependency injection (AppComponent) - Include analytics tracking and source identification for launches - Route to EditPostActivity for now (scaffold implementation) This provides infrastructure for clean editor activity separation while maintaining backward compatibility. Future changes will: - Add EditorLauncherParams data class with builder pattern - Route to EditPostGutenbergKitActivity when implemented - Add redirection fallback with monitoring Covers ActivityLauncher methods: - openEditorForSiteInNewStack() - openEditorForPostInNewStack() - openEditorForReblog() - addNewPostForResult() variants - editPostOrPageForResult() variants - editPageForResult() variants - addNewPageForResult() variants * Add EditorLauncherParams for type-safe editor launch parameters Replace Bundle-based Intent extras with type-safe parameter objects to improve maintainability and reduce errors. Changes: - Add EditorLauncherParams.kt with Kotlin data class and Java Builder pattern - Update EditorLauncher.kt to accept EditorLauncherParams instead of Intent bundles - Refactor ActivityLauncher.java methods to use EditorLauncherParams.Builder - Add backward compatibility with deprecated Intent-based method - Centralize all Intent extra handling in EditorLauncher.addEditorExtras() - Maintain analytics tracking with type-safe parameter source detection This provides compile-time safety while maintaining Java interoperability through the builder pattern. * Remove deprecated Intent-based EditorLauncher method Complete migration to EditorLauncherParams for all ActivityLauncher methods. Changes: - Remove deprecated createEditorIntent(context, Intent) method from EditorLauncher - Remove unused getIntentSource() legacy method - Update remaining ActivityLauncher methods to use EditorLauncherParams.Builder: - addNewPostWithContentFromAIForResult() - addNewPageForResult() (both Activity and Fragment variants) - All editor launches now use type-safe parameters instead of Bundle operations This completes the EditorLauncherParams migration for ActivityLauncher, providing compile-time safety and better maintainability. * Fix Detekt code quality violations in EditorLauncher Address all code quality issues identified by Detekt static analysis. Changes: - Remove unused imports (Bundle) and unused property (analyticsTracker) - Replace TODO comments with regular comments to avoid ForbiddenComment violations - Break down complex addEditorExtras() method into smaller helper methods: - addBasicExtras() - site, page, promo flags - addPostExtras() - post IDs, autosave, quickpress, landing editor flags - addReblogExtras() - reblog title, quote, image, citation, action - addPageExtras() - page title, content, template - addMiscExtras() - voice content, media, prompt ID, entry point - Fix MaxLineLength violations by splitting long builder method calls - Add missing newline at end of EditorLauncherParams.kt This reduces cyclomatic complexity and improves code maintainability while maintaining all existing functionality. * Move analytics source field handling into EditorLauncher Centralizes the EXTRA_CREATION_SOURCE_DETAIL Intent extra creation in EditorLauncher.addMiscExtras instead of requiring manual addition in each ActivityLauncher method. This eliminates code duplication and ensures analytics tracking is consistently applied across all editor launches. Changes: - Add AnalyticsUtils.EXTRA_CREATION_SOURCE_DETAIL to EditorLauncher.addMiscExtras - Remove redundant analytics field additions from ActivityLauncher methods - Add AnalyticsUtils import to EditorLauncher * Require non-null SiteModel in EditorLauncherParams.Builder constructor Makes the Java Builder pattern more type-safe by requiring SiteModel in the constructor rather than checking for null at build time. Changes: - EditorLauncherParams.Builder now takes @nonnull SiteModel in constructor - Remove site() setter method from Builder - Remove runtime requireNotNull check in build() - Update all call sites to pass site to constructor - Add missing @nonnull annotations in ActivityLauncher * Add analytics tracking to EditorLauncher Tracks editor launches with should_use_gutenberg_kit property to understand feature flag usage patterns. * Add documentation and test for EditorLauncherParams field mapping Ensures all EditorLauncherParams fields are handled in EditorLauncher.addEditorExtras() through documentation and a unit test that validates completeness. Changes: - Add cross-reference documentation in EditorLauncherParams and EditorLauncher - Add EditorLauncherTest with detailed field-to-method mapping documentation - Test validates that all fields are accounted for in addEditorExtras methods - Filter out Kotlin synthetic fields in test validation * Fix line length in EditorLauncherTest * Update analytics property name to will_use_gutenberg_kit * Add tracking for EditorLauncher vs direct Intent launches Adds a way to distinguish editor launches that went through EditorLauncher vs direct Intent creation for analytics purposes. Changes: - EditorLauncher: Add EXTRA_LAUNCHED_VIA_EDITOR_LAUNCHER intent extra - EditPostActivity: Track EDITOR_LAUNCHED_VIA_EDITOR_LAUNCHER when extra is present (only on initial creation, not configuration changes) - AnalyticsTracker: Add EDITOR_LAUNCHED_VIA_EDITOR_LAUNCHER stat * Fix line length in EditPostActivity * Add null checks for PostModel in editPostOrPageForResult methods Prevents crashes when PostModel is null (e.g., when mPostStore.getPostByLocalPostId returns null for non-existent posts) by showing a user-friendly error message instead. Changes: - Add null check and error toast in both editPostOrPageForResult variants - Prevents crashes from calling post.getId() or post.isPage() on null objects * Make all boolean parameters nullable to avoid unintended defaults The trunk editPostOrPageForResult methods never set boolean extras like EXTRA_IS_PAGE, EXTRA_IS_PROMO, or EXTRA_LOAD_AUTO_SAVE_REVISION unless explicitly required. Making all boolean parameters nullable ensures they're only set when explicitly provided, matching trunk behavior exactly. Changes: - Make all boolean fields nullable in EditorLauncherParams and Builder - Update EditorLauncher to only set extras when parameters are non-null - Remove unnecessary loadAutoSaveRevision(false) from first editPostOrPageForResult - Preserves explicit boolean settings in other methods (pages, promos, etc.) * Don't set source in ActivityLauncher.openEditorForReblog
1 parent a9f7a6c commit 37a5943

File tree

8 files changed

+433
-52
lines changed

8 files changed

+433
-52
lines changed

WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
import org.wordpress.android.ui.posts.prepublishing.categories.addcategory.PrepublishingAddCategoryFragment;
110110
import org.wordpress.android.ui.posts.prepublishing.home.PrepublishingHomeAdapter;
111111
import org.wordpress.android.ui.posts.prepublishing.home.PrepublishingHomeFragment;
112+
import org.wordpress.android.ui.posts.EditorLauncher;
112113
import org.wordpress.android.ui.posts.prepublishing.publishsettings.PrepublishingPublishSettingsFragment;
113114
import org.wordpress.android.ui.posts.prepublishing.social.PrepublishingSocialFragment;
114115
import org.wordpress.android.ui.posts.prepublishing.tags.PrepublishingTagsFragment;
@@ -561,4 +562,7 @@ public interface AppComponent {
561562
void inject(PostResolutionOverlayFragment object);
562563

563564
void inject(LoginSiteApplicationPasswordFragment object);
565+
566+
// Provide access to EditorLauncher for static utility classes
567+
EditorLauncher editorLauncher();
564568
}

WordPress/src/main/java/org/wordpress/android/ui/ActivityLauncher.java

Lines changed: 105 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,9 @@
8787
import org.wordpress.android.ui.plugins.PluginBrowserActivity;
8888
import org.wordpress.android.ui.plugins.PluginDetailActivity;
8989
import org.wordpress.android.ui.plugins.PluginUtils;
90-
import org.wordpress.android.ui.posts.EditPostActivity;
9190
import org.wordpress.android.ui.posts.EditPostActivityConstants;
91+
import org.wordpress.android.ui.posts.EditorLauncher;
92+
import org.wordpress.android.ui.posts.EditorLauncherParams;
9293
import org.wordpress.android.ui.posts.JetpackSecuritySettingsActivity;
9394
import org.wordpress.android.ui.posts.PostListType;
9495
import org.wordpress.android.ui.posts.PostUtils;
@@ -401,9 +402,11 @@ public static void openEditorForSiteInNewStack(Context context, @NonNull SiteMod
401402
TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);
402403
Intent mainActivityIntent = getMainActivityInNewStack(context);
403404

404-
Intent editorIntent = new Intent(context, EditPostActivity.class);
405-
editorIntent.putExtra(WordPress.SITE, site);
406-
editorIntent.putExtra(EditPostActivityConstants.EXTRA_IS_PAGE, false);
405+
EditorLauncherParams params = new EditorLauncherParams.Builder(site)
406+
.isPage(false)
407+
.build();
408+
409+
Intent editorIntent = EditorLauncher.getInstance().createEditorIntent(context, params);
407410

408411
taskStackBuilder.addNextIntent(mainActivityIntent);
409412
taskStackBuilder.addNextIntent(editorIntent);
@@ -415,10 +418,12 @@ public static void openEditorForPostInNewStack(Context context, @NonNull SiteMod
415418
TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(context);
416419
Intent mainActivityIntent = getMainActivityInNewStack(context);
417420

418-
Intent editorIntent = new Intent(context, EditPostActivity.class);
419-
editorIntent.putExtra(WordPress.SITE, site);
420-
editorIntent.putExtra(EditPostActivityConstants.EXTRA_POST_LOCAL_ID, localPostId);
421-
editorIntent.putExtra(EditPostActivityConstants.EXTRA_IS_PAGE, false);
421+
EditorLauncherParams params = new EditorLauncherParams.Builder(site)
422+
.postLocalId(localPostId)
423+
.isPage(false)
424+
.build();
425+
426+
Intent editorIntent = EditorLauncher.getInstance().createEditorIntent(context, params);
422427

423428
taskStackBuilder.addNextIntent(mainActivityIntent);
424429
taskStackBuilder.addNextIntent(editorIntent);
@@ -457,12 +462,15 @@ public static void openEditorForReblog(
457462
site.getSiteId()
458463
);
459464

460-
Intent editorIntent = new Intent(activity, EditPostActivity.class);
461-
editorIntent.putExtra(EditPostActivityConstants.EXTRA_REBLOG_POST_TITLE, post.getTitle());
462-
editorIntent.putExtra(EditPostActivityConstants.EXTRA_REBLOG_POST_QUOTE, post.getExcerpt());
463-
editorIntent.putExtra(EditPostActivityConstants.EXTRA_REBLOG_POST_IMAGE, post.getFeaturedImage());
464-
editorIntent.putExtra(EditPostActivityConstants.EXTRA_REBLOG_POST_CITATION, post.getUrl());
465-
editorIntent.setAction(EditPostActivityConstants.ACTION_REBLOG);
465+
EditorLauncherParams params = new EditorLauncherParams.Builder(site)
466+
.reblogPostTitle(post.getTitle())
467+
.reblogPostQuote(post.getExcerpt())
468+
.reblogPostImage(post.getFeaturedImage())
469+
.reblogPostCitation(post.getUrl())
470+
.reblogAction(EditPostActivityConstants.ACTION_REBLOG)
471+
.build();
472+
473+
Intent editorIntent = EditorLauncher.getInstance().createEditorIntent(activity, params);
466474

467475
addNewPostForResult(editorIntent, activity, site, false, reblogSource, -1, null);
468476
}
@@ -980,26 +988,35 @@ public static void addNewPostWithContentFromAIForResult(
980988
return;
981989
}
982990

983-
Intent intent = new Intent(activity, EditPostActivity.class);
984-
intent.putExtra(WordPress.SITE, site);
985-
intent.putExtra(EditPostActivityConstants.EXTRA_IS_PAGE, false);
986-
intent.putExtra(EditPostActivityConstants.EXTRA_IS_PROMO, isPromo);
987-
intent.putExtra(AnalyticsUtils.EXTRA_CREATION_SOURCE_DETAIL, source);
988-
intent.putExtra(EditPostActivityConstants.EXTRA_VOICE_CONTENT, content);
991+
EditorLauncherParams params = new EditorLauncherParams.Builder(site)
992+
.isPage(false)
993+
.isPromo(isPromo)
994+
.source(source)
995+
.voiceContent(content)
996+
.build();
997+
998+
Intent intent = EditorLauncher.getInstance().createEditorIntent(activity, params);
989999
activity.startActivityForResult(intent, RequestCodes.EDIT_POST);
9901000
}
9911001

9921002
public static void addNewPostForResult(
9931003
Activity activity,
994-
SiteModel site,
1004+
@NonNull SiteModel site,
9951005
boolean isPromo,
9961006
PagePostCreationSourcesDetail source,
9971007
final int promptId,
9981008
final EntryPoint entryPoint
9991009
) {
1000-
addNewPostForResult(
1001-
new Intent(activity, EditPostActivity.class), activity, site, isPromo, source, promptId, entryPoint
1002-
);
1010+
EditorLauncherParams params = new EditorLauncherParams.Builder(site)
1011+
.isPage(false)
1012+
.isPromo(isPromo)
1013+
.source(source)
1014+
.promptId(promptId)
1015+
.entryPoint(entryPoint)
1016+
.build();
1017+
1018+
Intent editorIntent = EditorLauncher.getInstance().createEditorIntent(activity, params);
1019+
activity.startActivityForResult(editorIntent, RequestCodes.EDIT_POST);
10031020
}
10041021

10051022
public static void addNewPostForResult(
@@ -1024,14 +1041,35 @@ public static void addNewPostForResult(
10241041
activity.startActivityForResult(intent, RequestCodes.EDIT_POST);
10251042
}
10261043

1027-
public static void editPostOrPageForResult(Activity activity, SiteModel site, PostModel post) {
1028-
editPostOrPageForResult(new Intent(activity, EditPostActivity.class), activity, site, post.getId(), false);
1044+
public static void editPostOrPageForResult(Activity activity, @NonNull SiteModel site, @Nullable PostModel post) {
1045+
if (post == null) {
1046+
ToastUtils.showToast(activity, R.string.post_not_found, ToastUtils.Duration.SHORT);
1047+
return;
1048+
}
1049+
1050+
EditorLauncherParams params = new EditorLauncherParams.Builder(site)
1051+
.postLocalId(post.getId())
1052+
.loadAutoSaveRevision(false)
1053+
.build();
1054+
1055+
Intent editorIntent = EditorLauncher.getInstance().createEditorIntent(activity, params);
1056+
activity.startActivityForResult(editorIntent, RequestCodes.EDIT_POST);
10291057
}
10301058

1031-
public static void editPostOrPageForResult(Activity activity, SiteModel site, PostModel post,
1059+
public static void editPostOrPageForResult(Activity activity, @NonNull SiteModel site, @Nullable PostModel post,
10321060
boolean loadAutoSaveRevision) {
1033-
editPostOrPageForResult(new Intent(activity, EditPostActivity.class), activity, site, post.getId(),
1034-
loadAutoSaveRevision);
1061+
if (post == null) {
1062+
ToastUtils.showToast(activity, R.string.post_not_found, ToastUtils.Duration.SHORT);
1063+
return;
1064+
}
1065+
1066+
EditorLauncherParams params = new EditorLauncherParams.Builder(site)
1067+
.postLocalId(post.getId())
1068+
.loadAutoSaveRevision(loadAutoSaveRevision)
1069+
.build();
1070+
1071+
Intent editorIntent = EditorLauncher.getInstance().createEditorIntent(activity, params);
1072+
activity.startActivityForResult(editorIntent, RequestCodes.EDIT_POST);
10351073
}
10361074

10371075
public static void editPostOrPageForResult(Intent intent, Activity activity, SiteModel site, int postLocalId) {
@@ -1056,8 +1094,14 @@ public static void editPostOrPageForResult(Intent intent, Activity activity, Sit
10561094

10571095
public static void editPageForResult(@NonNull Fragment fragment, @NonNull SiteModel site,
10581096
int pageLocalId, boolean loadAutoSaveRevision) {
1059-
Intent intent = new Intent(fragment.getContext(), EditPostActivity.class);
1060-
editPageForResult(intent, fragment, site, pageLocalId, loadAutoSaveRevision, RequestCodes.EDIT_POST);
1097+
EditorLauncherParams params = new EditorLauncherParams.Builder(site)
1098+
.postLocalId(pageLocalId)
1099+
.loadAutoSaveRevision(loadAutoSaveRevision)
1100+
.isPage(true)
1101+
.build();
1102+
1103+
Intent intent = EditorLauncher.getInstance().createEditorIntent(fragment.getContext(), params);
1104+
fragment.startActivityForResult(intent, RequestCodes.EDIT_POST);
10611105
}
10621106

10631107
public static void editPageForResult(Intent intent, @NonNull Fragment fragment, @NonNull SiteModel site,
@@ -1067,10 +1111,16 @@ public static void editPageForResult(Intent intent, @NonNull Fragment fragment,
10671111

10681112
public static void editLandingPageForResult(@NonNull Fragment fragment, @NonNull SiteModel site, int homeLocalId,
10691113
boolean isNewSite) {
1070-
Intent intent = new Intent(fragment.getContext(), EditPostActivity.class);
1071-
intent.putExtra(EditPostActivityConstants.EXTRA_IS_LANDING_EDITOR, true);
1072-
intent.putExtra(EditPostActivityConstants.EXTRA_IS_LANDING_EDITOR_OPENED_FOR_NEW_SITE, isNewSite);
1073-
editPageForResult(intent, fragment, site, homeLocalId, false, RequestCodes.EDIT_LANDING_PAGE);
1114+
EditorLauncherParams params = new EditorLauncherParams.Builder(site)
1115+
.postLocalId(homeLocalId)
1116+
.loadAutoSaveRevision(false)
1117+
.isPage(true)
1118+
.isLandingEditor(true)
1119+
.isLandingEditorOpenedForNewSite(isNewSite)
1120+
.build();
1121+
1122+
Intent intent = EditorLauncher.getInstance().createEditorIntent(fragment.getContext(), params);
1123+
fragment.startActivityForResult(intent, RequestCodes.EDIT_LANDING_PAGE);
10741124
}
10751125

10761126
public static void editPageForResult(Intent intent, @NonNull Fragment fragment, @NonNull SiteModel site,
@@ -1089,14 +1139,16 @@ public static void addNewPageForResult(
10891139
@Nullable String template,
10901140
@NonNull PagePostCreationSourcesDetail source
10911141
) {
1092-
Intent intent = new Intent(activity, EditPostActivity.class);
1093-
intent.putExtra(WordPress.SITE, site);
1094-
intent.putExtra(EditPostActivityConstants.EXTRA_IS_PAGE, true);
1095-
intent.putExtra(EditPostActivityConstants.EXTRA_IS_PROMO, false);
1096-
intent.putExtra(EditPostActivityConstants.EXTRA_PAGE_TITLE, title);
1097-
intent.putExtra(EditPostActivityConstants.EXTRA_PAGE_CONTENT, content);
1098-
intent.putExtra(EditPostActivityConstants.EXTRA_PAGE_TEMPLATE, template);
1099-
intent.putExtra(AnalyticsUtils.EXTRA_CREATION_SOURCE_DETAIL, source);
1142+
EditorLauncherParams params = new EditorLauncherParams.Builder(site)
1143+
.isPage(true)
1144+
.isPromo(false)
1145+
.pageTitle(title)
1146+
.pageContent(content)
1147+
.pageTemplate(template)
1148+
.source(source)
1149+
.build();
1150+
1151+
Intent intent = EditorLauncher.getInstance().createEditorIntent(activity, params);
11001152
activity.startActivityForResult(intent, RequestCodes.EDIT_POST);
11011153
}
11021154

@@ -1107,14 +1159,16 @@ public static void addNewPageForResult(
11071159
@NonNull String content,
11081160
@Nullable String template,
11091161
@NonNull PagePostCreationSourcesDetail source) {
1110-
Intent intent = new Intent(fragment.getContext(), EditPostActivity.class);
1111-
intent.putExtra(WordPress.SITE, site);
1112-
intent.putExtra(EditPostActivityConstants.EXTRA_IS_PAGE, true);
1113-
intent.putExtra(EditPostActivityConstants.EXTRA_IS_PROMO, false);
1114-
intent.putExtra(EditPostActivityConstants.EXTRA_PAGE_TITLE, title);
1115-
intent.putExtra(EditPostActivityConstants.EXTRA_PAGE_CONTENT, content);
1116-
intent.putExtra(EditPostActivityConstants.EXTRA_PAGE_TEMPLATE, template);
1117-
intent.putExtra(AnalyticsUtils.EXTRA_CREATION_SOURCE_DETAIL, source);
1162+
EditorLauncherParams params = new EditorLauncherParams.Builder(site)
1163+
.isPage(true)
1164+
.isPromo(false)
1165+
.pageTitle(title)
1166+
.pageContent(content)
1167+
.pageTemplate(template)
1168+
.source(source)
1169+
.build();
1170+
1171+
Intent intent = EditorLauncher.getInstance().createEditorIntent(fragment.getContext(), params);
11181172
fragment.startActivityForResult(intent, RequestCodes.EDIT_POST);
11191173
}
11201174

WordPress/src/main/java/org/wordpress/android/ui/notifications/NotificationsListFragmentPage.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,9 @@ class NotificationsListFragmentPage : ViewPagerFragment(R.layout.notifications_l
316316
return
317317
}
318318
if (tabPosition == Unread.ordinal) {
319-
ActivityLauncher.addNewPostForResult(activity, selectedSite, false, POST_FROM_NOTIFS_EMPTY_VIEW, -1, null)
319+
selectedSite?.let {
320+
ActivityLauncher.addNewPostForResult(activity, it, false, POST_FROM_NOTIFS_EMPTY_VIEW, -1, null)
321+
}
320322
} else if (activity is WPMainActivity) {
321323
(requireActivity() as WPMainActivity).setReaderPageActive()
322324
}

WordPress/src/main/java/org/wordpress/android/ui/posts/EditPostActivity.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,12 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor
527527
(application as WordPress).component().inject(this)
528528
initializeViewModels()
529529

530+
// Track if this editor launch came via EditorLauncher (only on initial creation)
531+
if (savedInstanceState == null
532+
&& intent.getBooleanExtra(EditorLauncher.EXTRA_LAUNCHED_VIA_EDITOR_LAUNCHER, false)) {
533+
analyticsTrackerWrapper.track(Stat.EDITOR_LAUNCHED_VIA_EDITOR_LAUNCHER)
534+
}
535+
530536
setContentView(R.layout.new_edit_post_activity)
531537
val callback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
532538
override fun handleOnBackPressed() {

0 commit comments

Comments
 (0)