diff --git a/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java b/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java index 1276189902fa..53b8239ecc82 100644 --- a/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java +++ b/WordPress/src/main/java/org/wordpress/android/modules/AppComponent.java @@ -90,7 +90,6 @@ import org.wordpress.android.ui.posts.EditPostActivity; import org.wordpress.android.ui.posts.EditPostPublishSettingsFragment; import org.wordpress.android.ui.posts.EditPostSettingsFragment; -import org.wordpress.android.ui.posts.editor.GutenbergKitEditorFragment; import org.wordpress.android.ui.posts.HistoryListFragment; import org.wordpress.android.ui.posts.PostDatePickerDialogFragment; import org.wordpress.android.ui.posts.PostListFragment; @@ -273,8 +272,6 @@ public interface AppComponent { void inject(EditPostSettingsFragment object); - void inject(GutenbergKitEditorFragment object); - void inject(PostSettingsListDialogFragment object); void inject(PostsListActivity object); diff --git a/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java b/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java index 2696babf626c..66dc66748712 100644 --- a/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java +++ b/WordPress/src/main/java/org/wordpress/android/modules/ViewModelModule.java @@ -35,7 +35,6 @@ import org.wordpress.android.ui.posts.EditPostPublishSettingsViewModel; import org.wordpress.android.ui.posts.EditorBloggingPromptsViewModel; import org.wordpress.android.ui.posts.EditorJetpackSocialViewModel; -import org.wordpress.android.ui.posts.GutenbergKitViewModel; import org.wordpress.android.ui.posts.navigation.EditPostNavigationViewModel; import org.wordpress.android.ui.posts.EditPostSettingsViewModel; import org.wordpress.android.ui.posts.PostListMainViewModel; @@ -308,11 +307,6 @@ abstract class ViewModelModule { @ViewModelKey(EditPostSettingsViewModel.class) abstract ViewModel editPostSettingsViewModel(EditPostSettingsViewModel viewModel); - @Binds - @IntoMap - @ViewModelKey(GutenbergKitViewModel.class) - abstract ViewModel gutenbergKitViewModel(GutenbergKitViewModel viewModel); - @Binds @IntoMap @ViewModelKey(ReaderCommentListViewModel.class) 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 aaca02e756e4..40a955f61da3 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 @@ -433,7 +433,6 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor private lateinit var editPostSettingsViewModel: EditPostSettingsViewModel private lateinit var prepublishingViewModel: PrepublishingViewModel private lateinit var editPostAuthViewModel: EditPostAuthViewModel - private lateinit var gutenbergKitViewModel: GutenbergKitViewModel private lateinit var siteModel: SiteModel @@ -646,7 +645,6 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor editPostSettingsViewModel = ViewModelProvider(this, viewModelFactory)[EditPostSettingsViewModel::class.java] prepublishingViewModel = ViewModelProvider(this, viewModelFactory)[PrepublishingViewModel::class.java] editPostAuthViewModel = ViewModelProvider(this, viewModelFactory)[EditPostAuthViewModel::class.java] - gutenbergKitViewModel = ViewModelProvider(this, viewModelFactory)[GutenbergKitViewModel::class.java] } private fun initializeSiteModel(savedInstanceState: Bundle?): Boolean { @@ -2608,17 +2606,13 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor val gutenbergWebViewAuthorizationData = createGutenbergWebViewAuthorizationData(isWpCom) val settings = createGutenbergKitSettings(isWpCom) - val fragment = GutenbergKitEditorFragment.newInstance( + return GutenbergKitEditorFragment.newInstance( getContext(), isNewPost, gutenbergWebViewAuthorizationData, jetpackFeatureRemovalPhaseHelper.shouldShowJetpackPoweredEditorFeatures(), + settings ) - - // Set settings in ViewModel for fragment to observe - gutenbergKitViewModel.updateEditorSettings(settings) - - return fragment } private fun createGutenbergWebViewAuthorizationData(isWpCom: Boolean): GutenbergWebViewAuthorizationData { @@ -2638,7 +2632,7 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor ) } - private fun createGutenbergKitSettings(isWpCom: Boolean): GutenbergKitSettings { + 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/" @@ -2646,27 +2640,28 @@ class EditPostActivity : BaseAppCompatActivity(), EditorFragmentActivity, Editor // Use the application password for self-hosted sites when available val authHeader = if (isWpCom) "Bearer ${accountStore.accessToken}" else "Basic " val siteApiNamespace = if (isWpCom) - listOf("sites/${site.siteId}/", "sites/${UrlUtils.removeScheme(siteURL)}/") - else emptyList() + arrayOf("sites/${site.siteId}/", "sites/${UrlUtils.removeScheme(siteURL)}/") + else arrayOf() val languageString = perAppLocaleManager.getCurrentLocaleLanguageCode() val wpcomLocaleSlug = languageString.replace("_", "-").lowercase() - return GutenbergKitSettings( - postId = editPostRepository.getPost()?.remotePostId?.toInt(), - postType = postType, - postTitle = editPostRepository.getPost()?.title, - postContent = editPostRepository.getPost()?.content, - siteURL = siteURL, - siteApiRoot = siteApiRoot, - namespaceExcludedPaths = listOf("/wpcom/v2/following/recommendations", "/wpcom/v2/following/mine"), - authHeader = authHeader, - siteApiNamespace = siteApiNamespace, - themeStyles = experimentalFeatures.isEnabled(Feature.EXPERIMENTAL_BLOCK_EDITOR_THEME_STYLES), - plugins = gutenbergKitPluginsFeature.isEnabled() && site.isWPCom, - locale = wpcomLocaleSlug, - cookies = editPostAuthViewModel.getCookiesForPrivateSites(site, privateAtomicCookie), - webViewGlobals = listOf( + return mutableMapOf( + "postId" to editPostRepository.getPost()?.remotePostId?.toInt(), + "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, + "siteApiNamespace" to siteApiNamespace, + "themeStyles" to experimentalFeatures.isEnabled(Feature.EXPERIMENTAL_BLOCK_EDITOR_THEME_STYLES), + // Limited to Simple sites until application passwords are supported + "plugins" to (gutenbergKitPluginsFeature.isEnabled() && site.isWPCom), + "locale" to wpcomLocaleSlug, + "cookies" to editPostAuthViewModel.getCookiesForPrivateSites(site, privateAtomicCookie), + "webViewGlobals" to listOf( WebViewGlobal( "_currentSiteType", when { diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/GutenbergKitViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/GutenbergKitViewModel.kt deleted file mode 100644 index e178f7281753..000000000000 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/GutenbergKitViewModel.kt +++ /dev/null @@ -1,45 +0,0 @@ -package org.wordpress.android.ui.posts - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import org.wordpress.gutenberg.WebViewGlobal -import java.io.Serializable -import javax.inject.Inject - -data class GutenbergKitSettings( - val postId: Int? = null, - val postType: String, - val postTitle: String? = null, - val postContent: String? = null, - val siteURL: String, - val siteApiRoot: String, - val namespaceExcludedPaths: List = emptyList(), - val authHeader: String, - val siteApiNamespace: List = emptyList(), - val themeStyles: Boolean = false, - val plugins: Boolean = false, - val locale: String, - val cookies: Map = emptyMap(), - val webViewGlobals: List = emptyList() -) : Serializable { - companion object { - private const val serialVersionUID: Long = 1L - } -} - -/** - * ViewModel for managing GutenbergKit editor settings and state. - * Handles communication between EditPostActivity and GutenbergKitEditorFragment. - */ -class GutenbergKitViewModel @Inject constructor() : ViewModel() { - private val _editorSettings = MutableLiveData() - val editorSettings: LiveData = _editorSettings - - /** - * Updates the editor settings. Called by EditPostActivity when creating the fragment. - */ - fun updateEditorSettings(settings: GutenbergKitSettings) { - _editorSettings.value = settings - } -} diff --git a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/GutenbergKitEditorFragment.kt b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/GutenbergKitEditorFragment.kt index 02e17fe483d4..de55e2c0b37a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/GutenbergKitEditorFragment.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/posts/editor/GutenbergKitEditorFragment.kt @@ -50,19 +50,13 @@ import org.wordpress.gutenberg.GutenbergView.TitleAndContentCallback import org.wordpress.gutenberg.GutenbergWebViewPool.getPreloadedWebView import org.wordpress.gutenberg.GutenbergWebViewPool.recycleWebView import org.wordpress.gutenberg.Media +import org.wordpress.gutenberg.WebViewGlobal +import java.io.Serializable import java.util.concurrent.CountDownLatch -import androidx.lifecycle.ViewModelProvider -import org.wordpress.android.ui.posts.GutenbergKitSettings -import org.wordpress.android.ui.posts.GutenbergKitViewModel -import org.wordpress.android.WordPress -import javax.inject.Inject class GutenbergKitEditorFragment : EditorFragmentAbstract(), EditorMediaUploadListener, IHistoryListener, EditorThemeUpdateListener, GutenbergDialogPositiveClickInterface, GutenbergDialogNegativeClickInterface, GutenbergNetworkConnectionListener { - @Inject lateinit var viewModelFactory: ViewModelProvider.Factory - private lateinit var gutenbergKitViewModel: GutenbergKitViewModel - private var gutenbergView: GutenbergView? = null private var isHtmlModeEnabled = false @@ -76,29 +70,12 @@ class GutenbergKitEditorFragment : EditorFragmentAbstract(), EditorMediaUploadLi private var isEditorDidMount = false private var rootView: View? = null - // Access settings through ViewModel - private val settings: GutenbergKitSettings? - get() = if (::gutenbergKitViewModel.isInitialized) { - gutenbergKitViewModel.editorSettings.value - } else { - null - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ProfilingUtils.start("Visual Editor Startup") ProfilingUtils.split("EditorFragment.onCreate") - // Trigger dependency injection - (requireActivity().applicationContext as WordPress).component().inject(this) - - // Initialize shared ViewModel (same scope as Activity) - after DI is complete - gutenbergKitViewModel = ViewModelProvider( - requireActivity(), - viewModelFactory - )[GutenbergKitViewModel::class.java] - if (savedInstanceState != null) { isHtmlModeEnabled = savedInstanceState.getBoolean(KEY_HTML_MODE_ENABLED) isEditorStarted = savedInstanceState.getBoolean(KEY_EDITOR_STARTED) @@ -110,6 +87,11 @@ class GutenbergKitEditorFragment : EditorFragmentAbstract(), EditorMediaUploadLi override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { + if (arguments != null) { + @Suppress("UNCHECKED_CAST", "DEPRECATION") + settings = requireArguments().getSerializable(ARG_GUTENBERG_KIT_SETTINGS) as Map? + } + // request dependency injection. Do this after setting min/max dimensions if (activity is EditorFragmentActivity) { (activity as EditorFragmentActivity).initializeEditorFragment() @@ -235,6 +217,17 @@ class GutenbergKitEditorFragment : EditorFragmentAbstract(), EditorMediaUploadLi } } + // Type-safe settings accessors + private inline fun Map.getSetting(key: String): T? = this[key] as? T + private inline fun Map.getSettingOrDefault(key: String, default: T): T = + getSetting(key) ?: default + + private fun Map.getStringArray(key: String): Array = + getSetting>(key)?.asSequence()?.filterNotNull()?.toList()?.toTypedArray() ?: emptyArray() + + private fun Map.getWebViewGlobals(key: String): List = + getSetting>(key) ?: emptyList() + // View extension functions private fun View?.setVisibleOrGone(visible: Boolean) { this?.visibility = if (visible) View.VISIBLE else View.GONE @@ -524,31 +517,39 @@ class GutenbergKitEditorFragment : EditorFragmentAbstract(), EditorMediaUploadLi } private fun buildEditorConfiguration(editorSettings: String): EditorConfiguration { - val kitSettings = settings!! - - val postId = kitSettings.postId?.let { if (it == 0) -1 else it } - val firstNamespace = kitSettings.siteApiNamespace.firstOrNull() ?: "" - val editorAssetsEndpoint = "${kitSettings.siteApiRoot}wpcom/v2/${firstNamespace}editor-assets" - - return EditorConfiguration.Builder() - .setTitle(kitSettings.postTitle ?: "") - .setContent(kitSettings.postContent ?: "") - .setPostId(postId) - .setPostType(kitSettings.postType) - .setThemeStyles(kitSettings.themeStyles) - .setPlugins(kitSettings.plugins) - .setSiteApiRoot(kitSettings.siteApiRoot) - .setSiteApiNamespace(kitSettings.siteApiNamespace.toTypedArray()) - .setNamespaceExcludedPaths(kitSettings.namespaceExcludedPaths.toTypedArray()) - .setAuthHeader(kitSettings.authHeader) - .setWebViewGlobals(kitSettings.webViewGlobals) - .setEditorSettings(editorSettings) - .setLocale(kitSettings.locale) - .setEditorAssetsEndpoint(editorAssetsEndpoint) - .setCachedAssetHosts(setOf("s0.wp.com", UrlUtils.getHost(kitSettings.siteURL))) - .setEnableAssetCaching(true) - .setCookies(kitSettings.cookies) - .build() + val settingsMap = settings!! + + return settingsMap.run { + val postId = getSetting("postId").let { if (it == 0) -1 else it } + val siteURL = getSetting("siteURL") + val siteApiRoot = getSetting("siteApiRoot") + val siteApiNamespace = getStringArray("siteApiNamespace") + val firstNamespace = siteApiNamespace.firstOrNull() ?: "" + val editorAssetsEndpoint = "${siteApiRoot}wpcom/v2/${firstNamespace}editor-assets" + val cookies = getSetting>("cookies") ?: emptyMap() + val namespaceExcludedPaths = getStringArray("namespaceExcludedPaths") + val webViewGlobals = getWebViewGlobals("webViewGlobals") + + EditorConfiguration.Builder() + .setTitle(getSetting("postTitle") ?: "") + .setContent(getSetting("postContent") ?: "") + .setPostId(postId) + .setPostType(getSetting("postType")) + .setThemeStyles(getSettingOrDefault("themeStyles", false)) + .setPlugins(getSettingOrDefault("plugins", false)) + .setSiteApiRoot(getSetting("siteApiRoot") ?: "") + .setSiteApiNamespace(siteApiNamespace) + .setNamespaceExcludedPaths(namespaceExcludedPaths) + .setAuthHeader(getSetting("authHeader") ?: "") + .setWebViewGlobals(webViewGlobals) + .setEditorSettings(editorSettings) + .setLocale(getSetting("locale")) + .setEditorAssetsEndpoint(editorAssetsEndpoint) + .setCachedAssetHosts(setOf("s0.wp.com", UrlUtils.getHost(siteURL))) + .setEnableAssetCaching(true) + .setCookies(cookies) + .build() + } } override fun showNotice(message: String?) { @@ -588,22 +589,28 @@ class GutenbergKitEditorFragment : EditorFragmentAbstract(), EditorMediaUploadLi private const val ARG_GUTENBERG_WEB_VIEW_AUTH_DATA = "param_gutenberg_web_view_auth_data" const val ARG_FEATURED_IMAGE_ID: String = "featured_image_id" const val ARG_JETPACK_FEATURES_ENABLED: String = "jetpack_features_enabled" + const val ARG_GUTENBERG_KIT_SETTINGS: String = "gutenberg_kit_settings" private const val CAPTURE_PHOTO_PERMISSION_REQUEST_CODE = 101 private const val CAPTURE_VIDEO_PERMISSION_REQUEST_CODE = 102 + private var settings: Map? = null + fun newInstance( context: Context, isNewPost: Boolean, webViewAuthorizationData: GutenbergWebViewAuthorizationData?, jetpackFeaturesEnabled: Boolean, + settings: Map? ): GutenbergKitEditorFragment { val fragment = GutenbergKitEditorFragment() val args = Bundle() args.putBoolean(ARG_IS_NEW_POST, isNewPost) args.putBoolean(ARG_JETPACK_FEATURES_ENABLED, jetpackFeaturesEnabled) + args.putSerializable(ARG_GUTENBERG_KIT_SETTINGS, settings as Serializable?) fragment.setArguments(args) val db = getDatabase(context) + GutenbergKitEditorFragment.settings = settings db?.addParcel(ARG_GUTENBERG_WEB_VIEW_AUTH_DATA, webViewAuthorizationData) return fragment } diff --git a/WordPress/src/test/java/org/wordpress/android/ui/posts/GutenbergKitViewModelTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/posts/GutenbergKitViewModelTest.kt deleted file mode 100644 index c377373729f4..000000000000 --- a/WordPress/src/test/java/org/wordpress/android/ui/posts/GutenbergKitViewModelTest.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.wordpress.android.ui.posts - -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.assertj.core.api.Assertions.assertThat -import org.junit.Before -import org.junit.Test -import org.wordpress.android.BaseUnitTest - -@ExperimentalCoroutinesApi -class GutenbergKitViewModelTest : BaseUnitTest() { - private lateinit var viewModel: GutenbergKitViewModel - - @Before - fun setUp() { - viewModel = GutenbergKitViewModel() - } - - @Test - fun `updateEditorSettings stores and exposes settings correctly`() = test { - // Arrange - val testSettings = GutenbergKitSettings( - postId = 123, - postType = "post", - postTitle = "Test Post", - postContent = "Test content", - siteURL = "https://example.com", - siteApiRoot = "https://example.com/wp-json", - authHeader = "Bearer token123", - locale = "en" - ) - - // Act - viewModel.updateEditorSettings(testSettings) - - // Assert - assertThat(viewModel.editorSettings.value).isEqualTo(testSettings) - assertThat(viewModel.editorSettings.value?.postId).isEqualTo(123) - assertThat(viewModel.editorSettings.value?.postTitle).isEqualTo("Test Post") - } -}