Skip to content
Open
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
1 change: 1 addition & 0 deletions WordPress/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ dependencies {
}
}
implementation project(":libs:login")
implementation project(":libs:posttypes")
implementation("$gradle.ext.aboutAutomatticBinaryPath:${libs.versions.automattic.about.get()}")

implementation("$gradle.ext.gutenbergKitBinaryPath:${libs.versions.gutenberg.kit.get()}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@
import org.wordpress.android.ui.posts.PostUtils.EntryPoint;
import org.wordpress.android.ui.posts.PostsListActivity;
import org.wordpress.android.ui.posts.RemotePreviewLogicHelper.RemotePreviewType;
import org.wordpress.android.posttypes.CptPostTypesActivity;
import org.wordpress.android.posttypes.bridge.SiteReference;
import org.wordpress.android.ui.prefs.AccountSettingsActivity;
import org.wordpress.android.ui.prefs.AppSettingsActivity;
import org.wordpress.android.ui.prefs.BlogPreferencesActivity;
Expand Down Expand Up @@ -705,6 +707,16 @@ public static void viewCurrentBlogPages(@NonNull Context context, @NonNull SiteM
AnalyticsUtils.trackWithSiteDetails(AnalyticsTracker.Stat.OPENED_PAGES, site);
}

public static void viewPostTypes(@NonNull Context context, @NonNull SiteModel site) {
SiteReference siteRef = SiteReference.Companion.create(
site.getSiteId(),
site.getName() != null ? site.getName() : "",
site.getUrl() != null ? site.getUrl() : ""
);
Intent intent = CptPostTypesActivity.Companion.createIntent(context, siteRef);
context.startActivity(intent);
}

public static void viewCurrentBlogPagesOfType(Context context, SiteModel site, PageListType pageListType) {
if (pageListType == null) {
Intent intent = new Intent(context, PagesActivity.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -616,6 +616,8 @@ class MySiteFragment : Fragment(R.layout.my_site_fragment),
is SiteNavigationAction.OpenPlan -> ActivityLauncher.viewBlogPlans(activity, action.site)
is SiteNavigationAction.OpenPosts -> ActivityLauncher.viewCurrentBlogPosts(requireActivity(), action.site)
is SiteNavigationAction.OpenPages -> ActivityLauncher.viewCurrentBlogPages(requireActivity(), action.site)
is SiteNavigationAction.OpenPostTypes ->
ActivityLauncher.viewPostTypes(requireActivity(), action.site)
is SiteNavigationAction.OpenHomepage -> ActivityLauncher.editLandingPageForResult(
this,
action.site,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ sealed class SiteNavigationAction {
data class OpenPlan(val site: SiteModel) : SiteNavigationAction()
data class OpenPosts(val site: SiteModel) : SiteNavigationAction()
data class OpenPages(val site: SiteModel) : SiteNavigationAction()
data class OpenPostTypes(val site: SiteModel) : SiteNavigationAction()
data class OpenHomepage(
val site: SiteModel,
val homepageLocalId: Int,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class ListItemActionHandler @Inject constructor(
ListItemAction.PLAN -> SiteNavigationAction.OpenPlan(selectedSite)
ListItemAction.POSTS -> SiteNavigationAction.OpenPosts(selectedSite)
ListItemAction.PAGES -> SiteNavigationAction.OpenPages(selectedSite)
ListItemAction.POST_TYPES -> SiteNavigationAction.OpenPostTypes(selectedSite)
ListItemAction.ADMIN -> SiteNavigationAction.OpenAdmin(selectedSite)
ListItemAction.SUBSCRIBERS -> SiteNavigationAction.OpenSubscribers(selectedSite)
ListItemAction.PEOPLE -> SiteNavigationAction.OpenPeople(selectedSite)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ enum class ListItemAction (val trackingLabel: String) {
PLAN("plan"),
POSTS("posts"),
PAGES("pages"),
POST_TYPES("post_types"),
ADMIN("admin"),
PEOPLE("people"),
SUBSCRIBERS("subscribers"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import org.wordpress.android.ui.mysite.cards.quickstart.QuickStartRepository
import org.wordpress.android.ui.mysite.items.listitem.ListItemAction.COMMENTS
import org.wordpress.android.ui.mysite.items.listitem.ListItemAction.MEDIA
import org.wordpress.android.ui.mysite.items.listitem.ListItemAction.POSTS
import org.wordpress.android.ui.mysite.items.listitem.ListItemAction.POST_TYPES
import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures
import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures.Feature
import org.wordpress.android.ui.utils.ListItemInteraction
import org.wordpress.android.ui.utils.UiString
import org.wordpress.android.ui.utils.UiString.UiStringRes
Expand All @@ -21,7 +24,8 @@ import javax.inject.Inject
class SiteItemsBuilder @Inject constructor(
private val siteListItemBuilder: SiteListItemBuilder,
private val quickStartRepository: QuickStartRepository,
private val jetpackFeatureRemovalOverlayUtil: JetpackFeatureRemovalOverlayUtil
private val jetpackFeatureRemovalOverlayUtil: JetpackFeatureRemovalOverlayUtil,
private val experimentalFeatures: ExperimentalFeatures
) {
@Suppress("LongMethod")
fun build(params: SiteItemsBuilderParams): List<MySiteCardAndItem> {
Expand Down Expand Up @@ -49,6 +53,7 @@ class SiteItemsBuilder @Inject constructor(
listItemAction = POSTS
),
siteListItemBuilder.buildPagesItemIfAvailable(params.site, params.onClick, showPagesFocusPoint),
buildPostTypesItemIfEnabled(params.onClick),
ListItem(
R.drawable.ic_media_white_24dp,
UiStringRes(R.string.media),
Expand All @@ -65,6 +70,19 @@ class SiteItemsBuilder @Inject constructor(
)
}

private fun buildPostTypesItemIfEnabled(onClick: (ListItemAction) -> Unit): ListItem? {
return if (experimentalFeatures.isEnabled(Feature.EXPERIMENTAL_POST_TYPES)) {
ListItem(
R.drawable.ic_posts_white_24dp,
UiStringRes(R.string.post_types_screen_title),
onClick = ListItemInteraction.create(POST_TYPES, onClick),
listItemAction = POST_TYPES
)
} else {
null
}
}

private fun getTrafficSiteItems(
params: SiteItemsBuilderParams
): List<MySiteCardAndItem> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ class MenuActivity : BaseAppCompatActivity() {
is SiteNavigationAction.OpenPlan -> ActivityLauncher.viewBlogPlans(this, action.site)
is SiteNavigationAction.OpenPosts -> ActivityLauncher.viewCurrentBlogPosts(this, action.site)
is SiteNavigationAction.OpenPages -> ActivityLauncher.viewCurrentBlogPages(this, action.site)
is SiteNavigationAction.OpenPostTypes -> ActivityLauncher.viewPostTypes(this, action.site)
is SiteNavigationAction.OpenAdmin -> ActivityLauncher.viewBlogAdmin(this, action.site)
is SiteNavigationAction.OpenPeople -> ActivityLauncher.viewCurrentBlogPeople(this, action.site)
is SiteNavigationAction.OpenSharing -> ActivityLauncher.viewBlogSharing(this, action.site)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ class ExperimentalFeatures @Inject constructor(
"network_debugging",
R.string.experimental_network_debugging,
R.string.experimental_network_debugging_description
),
EXPERIMENTAL_POST_TYPES(
"experimental_post_types",
R.string.experimental_post_types,
R.string.experimental_post_types_description
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.wordpress.android.BuildConfig
import org.wordpress.android.analytics.AnalyticsTracker
import org.wordpress.android.analytics.AnalyticsTracker.Stat
import org.wordpress.android.fluxc.utils.AppLogWrapper
Expand Down Expand Up @@ -50,7 +51,10 @@ internal class ExperimentalFeaturesViewModel @Inject constructor(
}

private fun shouldShowFeature(feature: Feature): Boolean {
return if (gutenbergKitFeature.isEnabled()) {
// Only show Post Types feature in debug builds
return if (feature == Feature.EXPERIMENTAL_POST_TYPES) {
BuildConfig.DEBUG
} else if (gutenbergKitFeature.isEnabled()) {
feature != Feature.EXPERIMENTAL_BLOCK_EDITOR
} else {
feature != Feature.DISABLE_EXPERIMENTAL_BLOCK_EDITOR
Expand Down
3 changes: 3 additions & 0 deletions WordPress/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,9 @@
<string name="network_requests_disable_tracking_title">Disable Tracking?</string>
<string name="network_requests_disable_tracking_description">Existing logs will expire based on the retention period. To clear logs now, tap \"View Network Requests\" and use the trash icon before disabling.</string>
<string name="network_requests_disable">Disable</string>
<string name="experimental_post_types">Post Types</string>
<string name="experimental_post_types_description">Enable experimental post types screen</string>
<string name="post_types_screen_title">Post Types</string>

<!-- Debug settings -->
<string name="preference_open_debug_settings" translatable="false">Debug Settings</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.wordpress.android.ui.mysite.MySiteCardAndItemBuilderParams.SiteItemsB
import org.wordpress.android.ui.mysite.cards.quickstart.QuickStartRepository
import org.wordpress.android.ui.mysite.items.listitem.SiteItemsBuilder
import org.wordpress.android.ui.mysite.items.listitem.SiteListItemBuilder
import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeatures
import org.wordpress.android.ui.quickstart.QuickStartType

@RunWith(MockitoJUnitRunner::class)
Expand All @@ -35,6 +36,9 @@ class SiteItemsBuilderTest {
@Mock
lateinit var jetpackFeatureRemovalOverlayUtil: JetpackFeatureRemovalOverlayUtil

@Mock
lateinit var experimentalFeatures: ExperimentalFeatures

private lateinit var siteItemsBuilder: SiteItemsBuilder

@Before
Expand All @@ -45,7 +49,8 @@ class SiteItemsBuilderTest {
siteItemsBuilder = SiteItemsBuilder(
siteListItemBuilder,
quickStartRepository,
jetpackFeatureRemovalOverlayUtil
jetpackFeatureRemovalOverlayUtil,
experimentalFeatures
)
}

Expand Down
151 changes: 151 additions & 0 deletions libs/posttypes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
# Post Types Module

This module contains the experimental Custom Post Types (CPT) feature implementation.

## Purpose

The Post Types module is **intentionally isolated** from the main WordPress app module to:

1. **Prevent accidental FluxC usage** - By not depending on FluxC, the compiler catches any
accidental imports of legacy patterns. This is a key goal during wordpress-rs integration.

2. **Enable clean wordpress-rs integration** - Forces deliberate decisions about data models and
API integration from the start, rather than falling back to familiar FluxC patterns.

3. **Establish new architectural patterns** - Acts as a sandbox for the new service layer before
patterns are propagated to the rest of the codebase.

## Module Structure

```
libs/posttypes/
├── build.gradle
├── README.md
└── src/main/
├── AndroidManifest.xml
├── java/org/wordpress/android/posttypes/
│ ├── bridge/ # ⚠️ Temporary bridging code
│ │ ├── package-info.kt # Documentation for bridge package
│ │ ├── ActivitySetup.kt # CptActivity interface + applyBaseSetup()
│ │ ├── BridgeConstants.kt # Intent keys mirroring main app
│ │ ├── BridgeTheme.kt # Standalone Material3 theme
│ │ └── SiteReference.kt # Minimal site representation
│ ├── CptPostTypesActivity.kt
│ ├── CptPostTypesViewModel.kt
│ ├── CptFlatPostListActivity.kt
│ ├── CptFlatPostListViewModel.kt
│ └── compose/
│ ├── CptPostTypesScreen.kt
│ └── CptFlatPostListScreen.kt
└── res/values/
├── strings.xml
└── styles.xml # Cpt.NoActionBar theme
```

## The Bridge Package

The `bridge` package contains **temporary coupling code** that connects this isolated module to
the main WordPress app. Everything in this package is explicitly marked as "needs attention when
merging back or integrating wordpress-rs properly."

### Bridge Components

| Component | Purpose | Main App Equivalent |
|-----------|---------|---------------------|
| `CptActivity` + `applyBaseSetup()` | Activity setup (edge-to-edge, etc.) | `BaseAppCompatActivity` |
| `@style/Cpt.NoActionBar` | Activity theme (no action bar) | `@style/WordPress.NoActionBar` |
| `BridgeConstants.EXTRA_SITE` | Intent extra key for site data | `WordPress.SITE` |
| `CptTheme` | Standalone Material3 Compose theme | `AppThemeM3` |
| `SiteReference` | Minimal site data model | `SiteModel` (FluxC) |

### Why Composition Over Inheritance?

Activities implement `CptActivity` interface and call `applyBaseSetup()` instead of extending
a base class. This approach:

- Avoids "is-a" inheritance problems (fragile base class, tight coupling)
- Makes the setup explicit and discoverable
- Easier to migrate - just remove interface and inline or replace the setup
- Can be extended with additional lifecycle hooks if needed

## Migration Guide

### Option A: Full wordpress-rs Integration (Recommended)

When wordpress-rs integration is complete:

1. Replace `SiteReference` with wordpress-rs site models
2. Replace `CptTheme` with `AppThemeM3` (or keep module theme if preferred)
3. Replace `BridgeConstants.EXTRA_SITE` with `WordPress.SITE`
4. For activities: either extend `BaseAppCompatActivity` or keep `CptActivity` pattern
5. Delete the entire `bridge` package
6. Keep module structure for continued isolation

### Option B: Merge Back to Main Module

If reverting to the main module structure:

1. Move all non-bridge files to:
```
WordPress/src/main/java/org/wordpress/android/ui/posttypes/
```

2. Replace bridge imports with main app equivalents:
- `CptActivity` + `applyBaseSetup()` → extend `BaseAppCompatActivity`
- `@style/Cpt.NoActionBar` → `@style/WordPress.NoActionBar` (in AndroidManifest)
- `CptTheme` → `AppThemeM3`
- `SiteReference` → `SiteModel`
- `BridgeConstants.EXTRA_SITE` → `WordPress.SITE`

3. Update `ActivityLauncher.viewPostTypes()` to pass `SiteModel` directly

4. Remove module from `settings.gradle`:
```diff
- include ':libs:posttypes'
```

5. Remove dependency from `WordPress/build.gradle`:
```diff
- implementation project(":libs:posttypes")
```

6. Add activities back to `WordPress/src/main/AndroidManifest.xml`

7. Delete `libs/posttypes/` directory

## Dependencies

This module intentionally has minimal dependencies:

- **AndroidX AppCompat** - Base activity class
- **Jetpack Compose** - UI framework
- **Hilt** - Dependency injection
- **Kotlin Parcelize** - For `SiteReference` parcelable

**Not included (by design):**
- FluxC - Networking/data layer
- WordPress utils - App-specific utilities
- Any other main app dependencies

## Adding New Features

When adding new functionality to this module:

1. **Do not add FluxC dependencies** - If you need site/post data, integrate with wordpress-rs
or add to the bridge package with clear documentation

2. **Document any new bridge items** - If you must add bridging code, add it to the `bridge`
package with migration notes in both the code and this README

3. **Keep the module self-contained** - The goal is isolation; dependencies should flow inward,
not outward

## Testing

```bash
# Build the module
./gradlew :libs:posttypes:assembleDebug

# Build the full app (verifies integration)
./gradlew assembleWordPressVanillaDebug
```
54 changes: 54 additions & 0 deletions libs/posttypes/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.google.dagger.hilt)
alias(libs.plugins.ksp)
alias(libs.plugins.dependency.analysis)
}

android {
namespace "org.wordpress.android.posttypes"

defaultConfig {
compileSdk rootProject.compileSdkVersion
minSdkVersion rootProject.minSdkVersion
targetSdkVersion rootProject.targetSdkVersion
}

compileOptions {
sourceCompatibility JvmTarget.fromTarget(libs.versions.java.get()).target
targetCompatibility JvmTarget.fromTarget(libs.versions.java.get()).target
}

buildFeatures {
compose true
}
}

dependencies {
// AndroidX
implementation libs.androidx.appcompat.main

// Compose
implementation platform(libs.androidx.compose.bom)
implementation libs.androidx.compose.ui.main
implementation libs.androidx.compose.material3
implementation libs.androidx.compose.material.icons.extended
implementation libs.androidx.compose.ui.tooling.preview
implementation libs.androidx.activity.compose

// Lifecycle & ViewModel
implementation libs.androidx.lifecycle.viewmodel.compose
implementation libs.androidx.lifecycle.runtime

// Hilt
implementation libs.google.dagger.hilt.android.main
ksp libs.google.dagger.hilt.compiler

// Debug
debugImplementation libs.androidx.compose.ui.tooling.main
}
Loading