diff --git a/WordPress/src/main/AndroidManifest.xml b/WordPress/src/main/AndroidManifest.xml index 01638ac32ba7..cbfa0a8d38f1 100644 --- a/WordPress/src/main/AndroidManifest.xml +++ b/WordPress/src/main/AndroidManifest.xml @@ -203,6 +203,11 @@ android:configChanges="locale|orientation|screenSize" android:label="@string/me_btn_app_settings" android:theme="@style/WordPress.NoActionBar" /> + ActivityLauncher.viewExperimentalFeatures(context) } + } + if (BuildConfig.DEBUG && BuildConfig.ENABLE_DEBUG_SETTINGS) { rowDebugSettings.isVisible = true debugSettingsDivider.isVisible = true diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java index b0da91659936..33073bab4f5b 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/AppSettingsFragment.java @@ -97,9 +97,6 @@ public class AppSettingsFragment extends PreferenceFragment private WPSwitchPreference mStripImageLocation; private WPSwitchPreference mReportCrashPref; private WPSwitchPreference mOpenWebLinksWithJetpack; - @Nullable private PreferenceScreen mExperimentalFeaturesSettings; - @Nullable private WPSwitchPreference mExperimentalBlockEditorPref; - @Nullable private WPSwitchPreference mExperimentalBlockEditorStylesPref; private Preference mWhatsNew; @@ -180,18 +177,12 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { .getPrefAndSetChangeListener(this, R.string.pref_key_site_video_encoder_bitrate, this); mPrivacySettings = (PreferenceScreen) WPPrefUtils .getPrefAndSetClickListener(this, R.string.pref_key_privacy_settings, this); - mExperimentalFeaturesSettings = (PreferenceScreen) WPPrefUtils - .getPrefAndSetClickListener(this, R.string.pref_key_experimental_features_settings, this); mStripImageLocation = (WPSwitchPreference) WPPrefUtils .getPrefAndSetChangeListener(this, R.string.pref_key_strip_image_location, this); mReportCrashPref = (WPSwitchPreference) WPPrefUtils .getPrefAndSetChangeListener(this, R.string.pref_key_send_crash, this); - mExperimentalBlockEditorPref = (WPSwitchPreference) WPPrefUtils - .getPrefAndSetChangeListener(this, R.string.pref_key_experimental_block_editor, this); - mExperimentalBlockEditorStylesPref = (WPSwitchPreference) WPPrefUtils - .getPrefAndSetChangeListener(this, R.string.pref_key_experimental_block_editor_theme_styles, this); mOpenWebLinksWithJetpack = (WPSwitchPreference) WPPrefUtils @@ -269,7 +260,6 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Override public void onViewStateRestored(Bundle savedInstanceState) { super.onViewStateRestored(savedInstanceState); addPrivacyToolbar(); - addExperimentalFeaturesToolbar(); } private void addJetpackBadgeAsFooterIfEnabled(LayoutInflater inflater, ListView listView) { @@ -415,8 +405,6 @@ public boolean onPreferenceClick(Preference preference) { return handleDevicePreferenceClick(); } else if (preference == mPrivacySettings) { return handlePrivacyClick(); - } else if (preference == mExperimentalFeaturesSettings) { - return handleExperimentalFeaturesClick(); } else if (preference == mWhatsNew) { return handleFeatureAnnouncementClick(); } else if (preference == mLanguagePreference) { @@ -485,12 +473,6 @@ public boolean onPreferenceChange(Preference preference, Object newValue) { } else if (preference == mReportCrashPref) { AnalyticsTracker.track(Stat.PRIVACY_SETTINGS_REPORT_CRASHES_TOGGLED, Collections .singletonMap(TRACK_ENABLED, newValue)); - } else if (preference == mExperimentalBlockEditorPref) { - AnalyticsTracker.track(Stat.EXPERIMENTAL_BLOCK_EDITOR_TOGGLED, Collections - .singletonMap(TRACK_ENABLED, newValue)); - } else if (preference == mExperimentalBlockEditorStylesPref) { - AnalyticsTracker.track(Stat.EXPERIMENTAL_BLOCK_EDITOR_THEME_STYLES_TOGGLED, Collections - .singletonMap(TRACK_ENABLED, newValue)); } else if (preference == mOpenWebLinksWithJetpack) { handleOpenLinksInJetpack((Boolean) newValue); } @@ -635,32 +617,6 @@ private boolean addPrivacyToolbar() { return true; } - private boolean handleExperimentalFeaturesClick() { - AnalyticsTracker.track(Stat.APP_SETTINGS_EXPERIMENTAL_FEATURES_TAPPED); - - boolean isToolbarAdded = addExperimentalFeaturesToolbar(); - - if (!isToolbarAdded) { - return false; - } - - AnalyticsTracker.track(Stat.EXPERIMENTAL_FEATURES_SETTINGS_OPENED); - return true; - } - - private boolean addExperimentalFeaturesToolbar() { - if (mExperimentalFeaturesSettings == null || !isAdded()) { - return false; - } - - String title = getString(R.string.preference_experimental_features_settings); - Dialog dialog = mExperimentalFeaturesSettings.getDialog(); - if (dialog != null) { - WPActivityUtils.addToolbarToDialog(this, dialog, title); - } - return true; - } - private boolean handleFeatureAnnouncementClick() { if (getActivity() instanceof AppCompatActivity) { AnalyticsTracker.track(Stat.FEATURE_ANNOUNCEMENT_SHOWN_FROM_APP_SETTINGS); diff --git a/WordPress/src/main/java/org/wordpress/android/ui/prefs/ExperimentalFeaturesActivity.kt b/WordPress/src/main/java/org/wordpress/android/ui/prefs/ExperimentalFeaturesActivity.kt new file mode 100644 index 000000000000..f63ac466f014 --- /dev/null +++ b/WordPress/src/main/java/org/wordpress/android/ui/prefs/ExperimentalFeaturesActivity.kt @@ -0,0 +1,194 @@ +package org.wordpress.android.ui.prefs + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import android.os.Bundle +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Switch +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.lifecycle.ViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import org.wordpress.android.R +import org.wordpress.android.ui.compose.theme.AppThemeM3 +import org.wordpress.android.ui.compose.unit.Margin +import org.wordpress.android.util.extensions.setContent + +val experimentalFeatures = listOf( + Feature(key = "experimental_block_editor"), + Feature(key = "experimental_block_editor_theme_styles") +) + +data class Feature( + val enabled: Boolean = false, + val key: String, +) + +class FeatureViewModel : ViewModel() { + private val _switchStates = MutableStateFlow>(emptyMap()) + val switchStates: StateFlow> = _switchStates.asStateFlow() + + init { + val initialStates = experimentalFeatures.associate { item -> + item.key to Feature(AppPrefs.getManualFeatureConfig(item.key), item.key) + } + _switchStates.value = initialStates + } + + fun toggleFeature(key: String, enabled: Boolean) { + _switchStates.update { currentStates -> + currentStates.toMutableMap().apply { + this[key] = Feature(enabled, key) + AppPrefs.setManualFeatureConfig(enabled, key) + } + } + } +} + +@AndroidEntryPoint +class ExperimentalFeaturesActivity : AppCompatActivity() { + private val viewModel: FeatureViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + AppThemeM3 { + val features by viewModel.switchStates.collectAsStateWithLifecycle() + + ExperimentalFeaturesScreen( + features = features, + onFeatureToggled = viewModel::toggleFeature, + onNavigateBack = onBackPressedDispatcher::onBackPressed + ) + } + } + } +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ExperimentalFeaturesScreen( + features: Map, + onFeatureToggled: (key: String, enabled: Boolean) -> Unit, + onNavigateBack: () -> Unit +) { + Scaffold( + topBar = { + TopAppBar( + title = { + Text(text = stringResource(R.string.experimental_features_screen_title)) + }, + navigationIcon = { + IconButton(onClick = onNavigateBack) { + Icon( + Icons.AutoMirrored.Filled.ArrowBack, + stringResource(R.string.back) + ) + } + }, + ) + }, + ) { innerPadding -> + Box(modifier = Modifier.padding(innerPadding)) { + Column { + features.forEach { (key, feature) -> + val context = LocalContext.current + val label = remember(key) { + context.getStringResourceByName(key) + } + + FeatureToggle( + key = key, + label = label, + enabled = feature.enabled, + onChange = onFeatureToggled, + ) + } + } + } + } +} + +@SuppressLint("DiscouragedApi") +fun Context.getStringResourceByName(name: String): String { + val resourceId = resources.getIdentifier(name, "string", packageName) + return if (resourceId != 0) getString(resourceId) else name +} + +@Composable +fun FeatureToggle( + key: String, + label: String, + enabled: Boolean, + onChange: (String, Boolean) -> Unit, +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .clickable { onChange(key, !enabled) } + .padding(horizontal = Margin.ExtraLarge.value, vertical = Margin.MediumLarge.value) + ) { + Spacer(modifier = Modifier.width(Margin.ExtraLarge.value)) + Text( + text = label, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + Spacer(modifier = Modifier.weight(1f)) + Switch( + checked = enabled, + onCheckedChange = { newValue -> + onChange(key, newValue) + }, + ) + } +} + +@Preview +@Preview(uiMode = UI_MODE_NIGHT_YES) +@Composable +fun ExperimentalFeaturesScreenPreview() { + AppThemeM3 { + val featuresStatusAlternated = remember { + experimentalFeatures.mapIndexed { index, feature -> + feature.key to feature.copy(enabled = index % 2 == 0) + }.toMap() + } + + ExperimentalFeaturesScreen( + features = featuresStatusAlternated, + onFeatureToggled = { _, _ -> }, + onNavigateBack = {} + ) + } +} diff --git a/WordPress/src/main/res/drawable/ic_science_24.xml b/WordPress/src/main/res/drawable/ic_science_24.xml new file mode 100644 index 000000000000..e3f2452d5a6b --- /dev/null +++ b/WordPress/src/main/res/drawable/ic_science_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/WordPress/src/main/res/layout/me_fragment.xml b/WordPress/src/main/res/layout/me_fragment.xml index 7b9436ecbc9e..b8dcc8e35475 100644 --- a/WordPress/src/main/res/layout/me_fragment.xml +++ b/WordPress/src/main/res/layout/me_fragment.xml @@ -197,6 +197,25 @@ + + + + + + + + + + wp_pref_privacy_settings wp_pref_send_usage_stats wp_pref_send_crash_stats - wp_pref_experimental_features_settings - MANUAL_FEATURE_CONFIGexperimental_block_editor - MANUAL_FEATURE_CONFIGexperimental_block_editor_theme_styles wp_pref_device_settings wp_pref_app_experimental_section wp_pref_language diff --git a/WordPress/src/main/res/values/strings.xml b/WordPress/src/main/res/values/strings.xml index 78401d0c00f3..a02a009a4378 100644 --- a/WordPress/src/main/res/values/strings.xml +++ b/WordPress/src/main/res/values/strings.xml @@ -940,9 +940,6 @@ Privacy notice for California users The California Consumer Privacy Act ("CCPA") requires us to provide California residents with some additional information about the categories of personal information we collect and share, where we get that personal information, and how and why we use it. Read CCPA privacy notice - Experimental features - Experimental block editor - Experimental block editor styles Remove location from media Appearance Light @@ -962,8 +959,13 @@ Current language: + + Experimental Features + Experimental block editor + Experimental block editor styles + - Debug settings + Debug Settings Remote features Features in development Remote Field configs @@ -2580,6 +2582,7 @@ Profile Photo App Settings + Experimental Features Help & Support Share WordPress with a friend About WordPress diff --git a/WordPress/src/main/res/xml/app_settings.xml b/WordPress/src/main/res/xml/app_settings.xml index ce2acc568786..87c72b1fdfee 100644 --- a/WordPress/src/main/res/xml/app_settings.xml +++ b/WordPress/src/main/res/xml/app_settings.xml @@ -63,23 +63,6 @@ - - - - - - - - diff --git a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java index 46efebe88cf2..01fae673f408 100644 --- a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java +++ b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java @@ -296,6 +296,7 @@ public enum Stat { ME_GRAVATAR_UPLOADED, ME_GRAVATAR_UPLOAD_UNSUCCESSFUL, ME_GRAVATAR_UPLOAD_EXCEPTION, + EXPERIMENTAL_FEATURES_OPENED, MY_SITE_ACCESSED("my_site_tab_accessed"), MY_SITE_ICON_TAPPED, MY_SITE_ICON_REMOVED, @@ -912,7 +913,6 @@ public enum Stat { SETTINGS_DID_CHANGE, APP_SETTINGS_APPEARANCE_CHANGED, APP_SETTINGS_PRIVACY_SETTINGS_TAPPED, - APP_SETTINGS_EXPERIMENTAL_FEATURES_TAPPED, APP_SETTINGS_OPEN_DEVICE_SETTINGS_TAPPED, APP_SETTINGS_MAX_IMAGE_SIZE_CHANGED, APP_SETTINGS_IMAGE_QUALITY_CHANGED, @@ -922,9 +922,6 @@ public enum Stat { APP_SETTINGS_VIDEO_QUALITY_CHANGED, PRIVACY_SETTINGS_OPENED, PRIVACY_SETTINGS_REPORT_CRASHES_TOGGLED, - EXPERIMENTAL_FEATURES_SETTINGS_OPENED, - EXPERIMENTAL_BLOCK_EDITOR_TOGGLED, - EXPERIMENTAL_BLOCK_EDITOR_THEME_STYLES_TOGGLED, SHARING_BUTTONS_EDIT_SHARING_BUTTONS_CHANGED, SHARING_BUTTONS_EDIT_MORE_SHARING_BUTTONS_CHANGED, PEOPLE_MANAGEMENT_USER_INVITED,