Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
import org.wordpress.android.ui.photopicker.MediaPickerLauncher;
import org.wordpress.android.ui.posts.EditorConstants;
import org.wordpress.android.ui.posts.EditorLauncher;
import org.wordpress.android.ui.posts.GutenbergKitAnnouncementBottomSheetFragment;
import org.wordpress.android.ui.posts.PostUtils.EntryPoint;
import org.wordpress.android.ui.prefs.AppPrefs;
import org.wordpress.android.ui.prefs.AppSettingsActivity;
Expand Down Expand Up @@ -672,6 +673,11 @@ private void initViewModel() {
.show(getSupportFragmentManager(), FeatureAnnouncementDialogFragment.TAG);
});

mViewModel.getOnGutenbergKitAnnouncementRequested().observe(this, siteUrl -> {
GutenbergKitAnnouncementBottomSheetFragment.newInstance(siteUrl)
.show(getSupportFragmentManager(), GutenbergKitAnnouncementBottomSheetFragment.TAG);
});

mFloatingActionButton.setOnClickListener(v -> {
PageType selectedPage = getSelectedPage();
if (selectedPage != null) mViewModel.onFabClicked(getSelectedSite(), selectedPage);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,10 @@ class EditorLauncher @Inject constructor(
* Determines if GutenbergKit editor should be used based on feature flags and post content.
*/
private fun shouldUseGutenbergKitEditor(params: EditorLauncherParams): Boolean {
val featureState = gutenbergKitFeatureChecker.getFeatureState()
val site = params.siteSource.getSite(siteStore)
val featureState = gutenbergKitFeatureChecker.getFeatureState(site)
val isGutenbergFeatureEnabled = featureState.isGutenbergKitEnabled

val site = params.siteSource.getSite(siteStore)
return when {
!isGutenbergFeatureEnabled -> {
logFeatureDisabledReason(featureState)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package org.wordpress.android.ui.posts

import android.os.Bundle
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.TextPaint
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.color.MaterialColors
import org.wordpress.android.R
import org.wordpress.android.ui.WPWebViewActivity
import org.wordpress.android.ui.prefs.AppPrefs

/**
* One-time announcement bottom sheet for the upcoming GutenbergKit editor.
* Opts the user in for the currently selected site, or dismisses. Provides a
* "Learn more" link that opens a web page.
*/
class GutenbergKitAnnouncementBottomSheetFragment : BottomSheetDialogFragment() {
private var siteUrl: String? = null
var onOptIn: (() -> Unit)? = null

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View = inflater.inflate(R.layout.gutenberg_kit_announcement_bottom_sheet, container, false)

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

// Prevent Material's BottomSheetDialog from applying status-bar insets as top padding.
ViewCompat.setOnApplyWindowInsetsListener(view) { v, _ ->
v.setPadding(v.paddingLeft, 0, v.paddingRight, v.paddingBottom)
WindowInsetsCompat.CONSUMED
}

siteUrl = arguments?.getString(ARG_SITE_URL)

bindBodyWithLearnMore(view.findViewById(R.id.body_text))

view.findViewById<MaterialButton>(R.id.try_now_button).setOnClickListener {
siteUrl?.let { AppPrefs.setGutenbergKitEnabledForSite(it, true) }
onOptIn?.invoke()
dismiss()
}

view.findViewById<MaterialButton>(R.id.maybe_later_button).setOnClickListener {
dismiss()
}
}

private fun bindBodyWithLearnMore(textView: TextView) {
val body = getString(R.string.gutenberg_kit_announcement_body)
val learnMore = getString(R.string.gutenberg_kit_announcement_learn_more)
val combined = SpannableStringBuilder(body).append(' ').append(learnMore)
val start = combined.length - learnMore.length
val end = combined.length
val color = MaterialColors.getColor(textView, androidx.appcompat.R.attr.colorPrimary)
combined.setSpan(object : ClickableSpan() {
override fun onClick(widget: View) {
WPWebViewActivity.openURL(
requireContext(),
getString(R.string.gutenberg_kit_learn_more_url)
)
}

override fun updateDrawState(ds: TextPaint) {
super.updateDrawState(ds)
ds.isUnderlineText = false
}
}, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
combined.setSpan(ForegroundColorSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = combined
textView.movementMethod = LinkMovementMethod.getInstance()
}

companion object {
const val TAG = "GutenbergKitAnnouncementBottomSheetFragment"
private const val ARG_SITE_URL = "site_url"

@JvmStatic
fun newInstance(siteUrl: String?): GutenbergKitAnnouncementBottomSheetFragment {
return GutenbergKitAnnouncementBottomSheetFragment().apply {
arguments = Bundle().apply { putString(ARG_SITE_URL, siteUrl) }
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.wordpress.android.ui.posts

import org.wordpress.android.fluxc.model.SiteModel
import org.wordpress.android.ui.prefs.AppPrefsWrapper
import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures
import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures.Feature
import org.wordpress.android.util.config.GutenbergKitFeature
Expand All @@ -13,49 +15,53 @@ import javax.inject.Singleton
@Singleton
class GutenbergKitFeatureChecker @Inject constructor(
private val experimentalFeatures: ExperimentalFeatures,
private val gutenbergKitFeature: GutenbergKitFeature
private val gutenbergKitFeature: GutenbergKitFeature,
private val appPrefsWrapper: AppPrefsWrapper
) {
/**
* Data class containing the state of all GutenbergKit-related feature flags.
*/
data class FeatureState(
val isExperimentalBlockEditorEnabled: Boolean,
val isGutenbergKitFeatureEnabled: Boolean,
val isDisableExperimentalBlockEditorEnabled: Boolean
val isDisableExperimentalBlockEditorEnabled: Boolean,
val isEnabledForSite: Boolean = false
) {
/**
* Determines if GutenbergKit should be enabled based on the feature states.
*/
val isGutenbergKitEnabled: Boolean
get() = (isExperimentalBlockEditorEnabled || isGutenbergKitFeatureEnabled) &&
get() = (isExperimentalBlockEditorEnabled ||
isGutenbergKitFeatureEnabled ||
isEnabledForSite) &&
!isDisableExperimentalBlockEditorEnabled
}

/**
* Gets the current state of all GutenbergKit-related feature flags.
*
* @return FeatureState containing all flag states and the computed enabled state
* Gets the current state of all GutenbergKit-related feature flags for the given site (if any).
*/
fun getFeatureState(): FeatureState {
@JvmOverloads
fun getFeatureState(site: SiteModel? = null): FeatureState {
return FeatureState(
isExperimentalBlockEditorEnabled = experimentalFeatures.isEnabled(Feature.EXPERIMENTAL_BLOCK_EDITOR),
isGutenbergKitFeatureEnabled = gutenbergKitFeature.isEnabled(),
isDisableExperimentalBlockEditorEnabled = experimentalFeatures.isEnabled(
Feature.DISABLE_EXPERIMENTAL_BLOCK_EDITOR
)
),
isEnabledForSite = site?.url?.let { appPrefsWrapper.isGutenbergKitEnabledForSite(it) } ?: false
)
}

/**
* Determines if GutenbergKit is enabled based on feature flags.
*
* The feature is enabled if:
* - Either the experimental block editor is enabled OR the GutenbergKit feature flag is on
* - AND the disable experimental block editor flag is NOT enabled
*
* @return true if GutenbergKit should be enabled, false otherwise
* Determines if GutenbergKit is enabled based on feature flags (and optional per-site opt-in).
*/
fun isGutenbergKitEnabled(): Boolean {
return getFeatureState().isGutenbergKitEnabled
@JvmOverloads
fun isGutenbergKitEnabled(site: SiteModel? = null): Boolean {
return getFeatureState(site).isGutenbergKitEnabled
}

/**
* Whether the user-facing remote feature flag is on (controls opt-in surfaces).
*/
fun isGutenbergKitRemoteFeatureEnabled(): Boolean = gutenbergKitFeature.isEnabled()
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ public enum DeletablePrefKey implements PrefKey {
SHOULD_AUTO_ENABLE_GUTENBERG_FOR_THE_NEW_POSTS_PHASE_2,
GUTENBERG_OPT_IN_DIALOG_SHOWN,
GUTENBERG_FOCAL_POINT_PICKER_TOOLTIP_SHOWN,
GUTENBERG_KIT_OPT_IN_SITES,

POST_LIST_AUTHOR_FILTER,
POST_LIST_VIEW_LAYOUT_TYPE,
Expand Down Expand Up @@ -318,6 +319,8 @@ public enum UndeletablePrefKey implements PrefKey {
// These preferences persist across logout/login cycles.
IS_TRACK_NETWORK_REQUESTS_ENABLED,
TRACK_NETWORK_REQUESTS_RETENTION_PERIOD,

GUTENBERG_KIT_ANNOUNCEMENT_SHOWN,
}

static SharedPreferences prefs() {
Expand Down Expand Up @@ -827,6 +830,51 @@ public static boolean isGutenbergInfoPopupDisplayed(String siteURL) {
return urls != null && urls.contains(siteURL);
}

public static boolean isGutenbergKitEnabledForSite(String siteURL) {
if (TextUtils.isEmpty(siteURL)) {
return false;
}
Set<String> urls;
try {
urls = prefs().getStringSet(DeletablePrefKey.GUTENBERG_KIT_OPT_IN_SITES.name(), null);
} catch (ClassCastException exp) {
return false;
}
return urls != null && urls.contains(siteURL);
}

public static void setGutenbergKitEnabledForSite(String siteURL, boolean enabled) {
if (TextUtils.isEmpty(siteURL)) {
return;
}
Set<String> urls;
try {
urls = prefs().getStringSet(DeletablePrefKey.GUTENBERG_KIT_OPT_IN_SITES.name(), null);
} catch (ClassCastException exp) {
return;
}

Set<String> newUrls = new HashSet<>();
if (urls != null) {
newUrls.addAll(urls);
}
if (enabled) {
newUrls.add(siteURL);
} else {
newUrls.remove(siteURL);
}

prefs().edit().putStringSet(DeletablePrefKey.GUTENBERG_KIT_OPT_IN_SITES.name(), newUrls).apply();
}

public static boolean wasGutenbergKitAnnouncementShown() {
return prefs().getBoolean(UndeletablePrefKey.GUTENBERG_KIT_ANNOUNCEMENT_SHOWN.name(), false);
}

public static void setGutenbergKitAnnouncementShown(boolean shown) {
prefs().edit().putBoolean(UndeletablePrefKey.GUTENBERG_KIT_ANNOUNCEMENT_SHOWN.name(), shown).apply();
}

public static void setGutenbergInfoPopupDisplayed(String siteURL, boolean isDisplayed) {
if (isGutenbergInfoPopupDisplayed(siteURL)) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,16 @@ class AppPrefsWrapper @Inject constructor(val buildConfigWrapper: BuildConfigWra
fun setStatsNewStatsSuggestionLastDismissedAt(timestamp: Long) =
AppPrefs.setStatsNewStatsSuggestionLastDismissedAt(timestamp)

fun isGutenbergKitEnabledForSite(siteUrl: String?): Boolean =
AppPrefs.isGutenbergKitEnabledForSite(siteUrl)

fun setGutenbergKitEnabledForSite(siteUrl: String?, enabled: Boolean) =
AppPrefs.setGutenbergKitEnabledForSite(siteUrl, enabled)

var wasGutenbergKitAnnouncementShown: Boolean
get() = AppPrefs.wasGutenbergKitAnnouncementShown()
set(value) = AppPrefs.setGutenbergKitAnnouncementShown(value)

companion object {
private const val LIGHT_MODE_ID = 0
private const val DARK_MODE_ID = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ public class SiteSettingsFragment extends PreferenceFragment
// Writing settings
private WPSwitchPreference mGutenbergDefaultForNewPosts;
private WPSwitchPreference mUseThemeStylesPref;
private WPSwitchPreference mGutenbergKitPref;
private DetailListPreference mCategoryPref;
private DetailListPreference mFormatPref;
private WPPreference mDateFormatPref;
Expand Down Expand Up @@ -848,6 +849,8 @@ public boolean onPreferenceChange(Preference preference, Object newValue) {
AnalyticsUtils.refreshMetadata(mAccountStore, mSiteStore);
} else if (preference == mUseThemeStylesPref) {
mSiteSettings.setUseThemeStyles((Boolean) newValue);
} else if (preference == mGutenbergKitPref) {
AppPrefs.setGutenbergKitEnabledForSite(mSite.getUrl(), (Boolean) newValue);
} else if (preference == mBloggingPromptsPref) {
final boolean isEnabled = (boolean) newValue;
mPromptsSettingsHelper.updatePromptsCardEnabledBlocking(mSite.getId(), isEnabled);
Expand Down Expand Up @@ -1037,6 +1040,10 @@ public void initPreferences() {
(WPSwitchPreference) getChangePref(R.string.pref_key_use_theme_styles);
mUseThemeStylesPref.setChecked(mSiteSettings.getUseThemeStyles());

mGutenbergKitPref =
(WPSwitchPreference) getChangePref(R.string.pref_key_gutenberg_kit_enabled);
mGutenbergKitPref.setChecked(AppPrefs.isGutenbergKitEnabledForSite(mSite.getUrl()));

mSiteAcceleratorSettings = (PreferenceScreen) getClickPref(R.string.pref_key_site_accelerator_settings);
mSiteAcceleratorSettingsNested =
(PreferenceScreen) getClickPref(R.string.pref_key_site_accelerator_settings_nested);
Expand Down Expand Up @@ -1083,6 +1090,12 @@ public void initPreferences() {
WPPrefUtils.removePreference(this, R.string.pref_key_site_editor, R.string.pref_key_use_theme_styles);
}

// hide the GutenbergKit opt-in switch unless the remote feature flag is on
if (!mGutenbergKitFeatureChecker.isGutenbergKitRemoteFeatureEnabled()) {
WPPrefUtils.removePreference(this, R.string.pref_key_site_editor,
R.string.pref_key_gutenberg_kit_enabled);
}

// hide Admin options depending of capabilities on this site
if ((!isAccessedViaWPComRest && !mSite.isSelfHostedAdmin())
|| (isAccessedViaWPComRest && !mSite.getHasCapabilityManageOptions())) {
Expand Down Expand Up @@ -1207,7 +1220,8 @@ public void setEditingEnabled(boolean enabled) {
mDateFormatPref, mTimeFormatPref, mTimezonePref, mBloggingRemindersPref, mPostsPerPagePref, mAmpPref,
mDeleteSitePref, mJpMonitorActivePref, mJpMonitorEmailNotesPref, mJpSsoPref,
mJpMonitorWpNotesPref, mJpBruteForcePref, mJpAllowlistPref, mJpMatchEmailPref, mJpUseTwoFactorPref,
mGutenbergDefaultForNewPosts, mUseThemeStylesPref, mHomepagePref, mBloggingPromptsPref
mGutenbergDefaultForNewPosts, mUseThemeStylesPref, mGutenbergKitPref, mHomepagePref,
mBloggingPromptsPref
};

for (Preference preference : editablePreference) {
Expand Down Expand Up @@ -1552,6 +1566,9 @@ public void setPreferencesFromSiteSettings() {
mWeekStartPref.setSummary(mWeekStartPref.getEntry());
mGutenbergDefaultForNewPosts.setChecked(SiteUtils.isBlockEditorDefaultForNewPost(mSite));
mUseThemeStylesPref.setChecked(mSiteSettings.getUseThemeStyles());
if (mGutenbergKitPref != null) {
mGutenbergKitPref.setChecked(AppPrefs.isGutenbergKitEnabledForSite(mSite.getUrl()));
}
setAdFreeHostingChecked(mSiteSettings.isAdFreeHostingEnabled());
boolean checked = mSiteSettings.isImprovedSearchEnabled() || mSiteSettings.getJetpackSearchEnabled();
mImprovedSearch.setChecked(checked);
Expand Down
Loading
Loading