Skip to content

Commit 5004b8a

Browse files
nbradburyclaude
andauthored
RS Post Settings: Read only settings screen (#22660)
* RS Posts: Resolve category and tag names on Post Settings screen Replace raw numeric IDs with actual category/tag names by fetching term data via the wordpress-rs terms API. Adds cached resolution methods to PostRsRestClient following the same pattern used for author names and featured images. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Simplify Post Settings code Remove unused UiState fields (status, format, authorId), consolidate duplicate resolve methods into a single resolveTermNames helper, collapse fetchCategoryNames/fetchTagNames wrappers into a single public fetchTermNames method, and remove unused fillMaxWidth import. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Use view context for term name resolution Authors lack edit permissions on terms, causing a 403 error and leaving categories/tags stuck on "Loading". Switch from listWithEditContext to listWithViewContext since we only need the term id and name. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Add inline progress and error states for async fields Replace raw string/list fields with FieldState sealed interface for author, categories, tags, and featured image. Shows a small spinner while resolving and an error message on failure instead of silently getting stuck. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Remove duplicate featured image label and show larger image The section header already provides the "Featured image" title, so drop the redundant headline from the loaded image row and render the image full-width with rounded corners. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Show dimmed "None" for empty featured image and excerpt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Fix dimmed "None" labels to render consistently Move dimmed color to supporting text instead of headline so both featured image and excerpt "None" labels have the same size and color. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Show dimmed "None" for all empty settings fields Add consistent "None" placeholder to categories, tags, author, slug, and password empty states matching featured image and excerpt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Extract fetchPost and resolveAsyncFields from loadPost Fixes detekt LongMethod violation by splitting loadPost into smaller focused methods. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Fix race condition and use localized format labels Use MutableStateFlow.update {} for atomic read-modify-write on concurrent async field resolution, and replace hardcoded English post format strings with existing string resources. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Add Compose previews for Post Settings screen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Fix detekt ReturnCount in formatPostFormatLabel Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Improve Post Settings UX with retry, chips, and shimmer Add error recovery with full-screen retry button and tap-to-retry on individual field errors. Render categories and tags as Material3 chips using FlowRow. Add shimmer placeholder and 16:9 aspect ratio for featured image. Make excerpt expandable when text overflows. Add accessibility announcements on loading states and bottom padding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Make Post Settings edge-to-edge with hero featured image Enable edge-to-edge display and move the featured image to the top of the screen as a full-bleed hero image with a floating back button. When no featured image is loaded, the screen falls back to the standard TopAppBar layout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Replace featured image section with top placeholder Always use the hero layout for Post Settings content. When there is no featured image, show a full-width "Featured image not set." placeholder at the top instead of the inline section. Remove the now-unused FeaturedImageField and FeaturedImageRow composables. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Simplify Post Settings code and fix Photon image sizing Remove dead code (unused onClick param, FEATURED_IMAGE retry branch), extract shared AsyncFieldRow composable to deduplicate Empty/Loading/Error handling, and fix Photon URLs to use screen width for hero images. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Fix review issues in Post Settings screen Add missing divider after author field, add accessibility click labels to ErrorFieldRow and ExpandableSettingsRow, and show distinct error message when featured image fails to load. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Deduplicate status-to-label mapping Reuse the shared toLabel() extension in formatStatusLabel() instead of duplicating the PostStatus-to-string-resource mapping. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Use ampersand in Date & Time label Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f9a59e0 commit 5004b8a

File tree

13 files changed

+1420
-51
lines changed

13 files changed

+1420
-51
lines changed

WordPress/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,10 @@
343343
android:name=".ui.postsrs.PostRsListActivity"
344344
android:theme="@style/WordPress.NoActionBar"
345345
android:label="" />
346+
<activity
347+
android:name=".ui.postsrs.PostRsSettingsActivity"
348+
android:theme="@style/WordPress.NoActionBar"
349+
android:label="" />
346350
<activity
347351
android:name=".ui.pages.PagesActivity"
348352
android:theme="@style/WordPress.NoActionBar"

WordPress/src/main/java/org/wordpress/android/ui/main/BaseAppCompatActivity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import org.wordpress.android.ui.notifications.NotificationsDetailActivity
3131
import org.wordpress.android.ui.posts.EditPostActivity
3232
import org.wordpress.android.ui.posts.GutenbergKitActivity
3333
import org.wordpress.android.ui.postsrs.PostRsListActivity
34+
import org.wordpress.android.ui.postsrs.PostRsSettingsActivity
3435
import org.wordpress.android.ui.posts.sharemessage.EditJetpackSocialShareMessageActivity
3536
import org.wordpress.android.ui.prefs.experimentalfeatures.ExperimentalFeaturesActivity
3637
import org.wordpress.android.ui.reader.ReaderCommentListActivity
@@ -100,6 +101,7 @@ private val excludedActivities = listOf(
100101
NewDomainSearchActivity::class.java.name,
101102
PersonalizationActivity::class.java.name,
102103
PostRsListActivity::class.java.name,
104+
PostRsSettingsActivity::class.java.name,
103105
PurchaseDomainActivity::class.java.name,
104106
SelfHostedUsersActivity::class.java.name,
105107
SiteMonitorParentActivity::class.java.name,

WordPress/src/main/java/org/wordpress/android/ui/postsrs/PostRsListActivity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ class PostRsListActivity : BaseAppCompatActivity() {
105105
this, false, event.blogId, event.postId,
106106
DirectOperation.COMMENT_JUMP, false
107107
)
108+
is PostRsListEvent.OpenPostSettings ->
109+
startActivity(PostRsSettingsActivity.createIntent(this, event.postId))
108110
is PostRsListEvent.ShowToast -> ToastUtils.showToast(this, event.messageResId)
109111
is PostRsListEvent.Finish -> finish()
110112
}

WordPress/src/main/java/org/wordpress/android/ui/postsrs/PostRsListEvent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,7 @@ sealed interface PostRsListEvent {
4646
val messageResId: Int
4747
) : PostRsListEvent
4848

49+
data class OpenPostSettings(val postId: Long) : PostRsListEvent
50+
4951
data object Finish : PostRsListEvent
5052
}

WordPress/src/main/java/org/wordpress/android/ui/postsrs/PostRsListUiState.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ enum class PostRsMenuAction(
7171
@DrawableRes val iconResId: Int,
7272
val isDestructive: Boolean = false
7373
) {
74+
SETTINGS(
75+
R.string.post_settings,
76+
R.drawable.ic_settings_white_24dp
77+
),
7478
VIEW(R.string.button_view, R.drawable.gb_ic_external),
7579
READ(
7680
R.string.button_read,
@@ -190,7 +194,7 @@ private fun FullEntityAnyPostWithEditContext.toUiModel(
190194
}
191195

192196
@StringRes
193-
private fun PostStatus?.toLabel(): Int = when (this) {
197+
internal fun PostStatus?.toLabel(): Int = when (this) {
194198
is PostStatus.Publish ->
195199
R.string.post_status_post_published
196200
is PostStatus.Draft -> R.string.post_status_draft

WordPress/src/main/java/org/wordpress/android/ui/postsrs/PostRsListViewModel.kt

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ class PostRsListViewModel @Inject constructor(
205205
val post = findPost(remotePostId)
206206

207207
when (action) {
208+
PostRsMenuAction.SETTINGS ->
209+
_events.trySend(PostRsListEvent.OpenPostSettings(remotePostId))
208210
PostRsMenuAction.VIEW -> {
209211
val url = post?.link
210212
if (url == null) {
@@ -476,48 +478,49 @@ class PostRsListViewModel @Inject constructor(
476478
tab: PostRsListTab,
477479
hasPassword: Boolean,
478480
commentsOpen: Boolean
479-
): List<PostRsMenuAction> = when (tab) {
480-
PostRsListTab.PUBLISHED ->
481-
getPublishedMenuActions(hasPassword, commentsOpen)
482-
PostRsListTab.DRAFTS -> buildList {
483-
add(PostRsMenuAction.VIEW)
484-
add(PostRsMenuAction.READ)
485-
if (site.hasCapabilityPublishPosts) {
486-
add(PostRsMenuAction.PUBLISH)
487-
}
488-
add(PostRsMenuAction.DUPLICATE)
489-
add(PostRsMenuAction.SHARE)
490-
add(PostRsMenuAction.TRASH)
491-
}
492-
PostRsListTab.SCHEDULED -> listOf(
493-
PostRsMenuAction.VIEW,
494-
PostRsMenuAction.READ,
495-
PostRsMenuAction.SHARE,
496-
PostRsMenuAction.TRASH
497-
)
498-
PostRsListTab.TRASHED -> listOf(
499-
PostRsMenuAction.MOVE_TO_DRAFT,
500-
PostRsMenuAction.DELETE_PERMANENTLY
501-
)
502-
}
503-
504-
private fun getPublishedMenuActions(
505-
hasPassword: Boolean,
506-
commentsOpen: Boolean
507481
): List<PostRsMenuAction> = buildList {
508-
add(PostRsMenuAction.VIEW)
509-
add(PostRsMenuAction.READ)
510-
add(PostRsMenuAction.MOVE_TO_DRAFT)
511-
add(PostRsMenuAction.DUPLICATE)
512-
add(PostRsMenuAction.SHARE)
513-
if (!hasPassword && blazeFeatureUtils.isSiteBlazeEligible(site)) {
514-
add(PostRsMenuAction.BLAZE)
515-
}
516-
if (SiteUtils.isAccessedViaWPComRest(site) && site.hasCapabilityViewStats) {
517-
add(PostRsMenuAction.STATS)
482+
when (tab) {
483+
PostRsListTab.PUBLISHED -> {
484+
add(PostRsMenuAction.SETTINGS)
485+
add(PostRsMenuAction.VIEW)
486+
add(PostRsMenuAction.READ)
487+
add(PostRsMenuAction.MOVE_TO_DRAFT)
488+
add(PostRsMenuAction.DUPLICATE)
489+
add(PostRsMenuAction.SHARE)
490+
if (!hasPassword && blazeFeatureUtils.isSiteBlazeEligible(site)) {
491+
add(PostRsMenuAction.BLAZE)
492+
}
493+
if (SiteUtils.isAccessedViaWPComRest(site) &&
494+
site.hasCapabilityViewStats
495+
) {
496+
add(PostRsMenuAction.STATS)
497+
}
498+
if (commentsOpen) add(PostRsMenuAction.COMMENTS)
499+
add(PostRsMenuAction.TRASH)
500+
}
501+
PostRsListTab.DRAFTS -> {
502+
add(PostRsMenuAction.SETTINGS)
503+
add(PostRsMenuAction.VIEW)
504+
add(PostRsMenuAction.READ)
505+
if (site.hasCapabilityPublishPosts) {
506+
add(PostRsMenuAction.PUBLISH)
507+
}
508+
add(PostRsMenuAction.DUPLICATE)
509+
add(PostRsMenuAction.SHARE)
510+
add(PostRsMenuAction.TRASH)
511+
}
512+
PostRsListTab.SCHEDULED -> {
513+
add(PostRsMenuAction.SETTINGS)
514+
add(PostRsMenuAction.VIEW)
515+
add(PostRsMenuAction.READ)
516+
add(PostRsMenuAction.SHARE)
517+
add(PostRsMenuAction.TRASH)
518+
}
519+
PostRsListTab.TRASHED -> {
520+
add(PostRsMenuAction.MOVE_TO_DRAFT)
521+
add(PostRsMenuAction.DELETE_PERMANENTLY)
522+
}
518523
}
519-
if (commentsOpen) add(PostRsMenuAction.COMMENTS)
520-
add(PostRsMenuAction.TRASH)
521524
}
522525

523526
/**
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.wordpress.android.ui.postsrs
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import android.os.Bundle
6+
import androidx.activity.enableEdgeToEdge
7+
import androidx.activity.viewModels
8+
import androidx.compose.runtime.collectAsState
9+
import androidx.compose.runtime.getValue
10+
import androidx.lifecycle.Lifecycle
11+
import androidx.lifecycle.lifecycleScope
12+
import androidx.lifecycle.repeatOnLifecycle
13+
import dagger.hilt.android.AndroidEntryPoint
14+
import kotlinx.coroutines.launch
15+
import org.wordpress.android.ui.compose.theme.AppThemeM3
16+
import org.wordpress.android.ui.main.BaseAppCompatActivity
17+
import org.wordpress.android.ui.postsrs.screens.PostRsSettingsScreen
18+
import org.wordpress.android.util.ToastUtils
19+
import org.wordpress.android.util.extensions.setContent
20+
21+
@AndroidEntryPoint
22+
class PostRsSettingsActivity : BaseAppCompatActivity() {
23+
private val viewModel: PostRsSettingsViewModel by viewModels()
24+
25+
override fun onCreate(savedInstanceState: Bundle?) {
26+
enableEdgeToEdge()
27+
super.onCreate(savedInstanceState)
28+
29+
observeEvents()
30+
31+
setContent {
32+
val uiState by viewModel.uiState.collectAsState()
33+
AppThemeM3 {
34+
PostRsSettingsScreen(
35+
uiState = uiState,
36+
onNavigateBack = {
37+
onBackPressedDispatcher.onBackPressed()
38+
},
39+
onRetry = viewModel::retry,
40+
onRetryField = viewModel::retryField,
41+
)
42+
}
43+
}
44+
}
45+
46+
private fun observeEvents() {
47+
lifecycleScope.launch {
48+
repeatOnLifecycle(Lifecycle.State.STARTED) {
49+
viewModel.events.collect { event ->
50+
when (event) {
51+
is PostRsSettingsEvent.Finish -> finish()
52+
is PostRsSettingsEvent.ShowSnackbar ->
53+
ToastUtils.showToast(
54+
this@PostRsSettingsActivity,
55+
event.message
56+
)
57+
}
58+
}
59+
}
60+
}
61+
}
62+
63+
companion object {
64+
fun createIntent(context: Context, postId: Long): Intent {
65+
return Intent(context, PostRsSettingsActivity::class.java)
66+
.putExtra(PostRsSettingsViewModel.EXTRA_POST_ID, postId)
67+
}
68+
}
69+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.wordpress.android.ui.postsrs
2+
3+
sealed interface FieldState {
4+
data object Empty : FieldState
5+
data object Loading : FieldState
6+
data class Loaded(val value: String) : FieldState
7+
data class Error(val message: String) : FieldState
8+
}
9+
10+
data class PostRsSettingsUiState(
11+
val isLoading: Boolean = true,
12+
val error: String? = null,
13+
val postTitle: String = "",
14+
val statusLabel: String = "",
15+
val publishDate: String = "",
16+
val password: String? = null,
17+
val authorName: FieldState = FieldState.Empty,
18+
val categoryNames: FieldState = FieldState.Empty,
19+
val tagNames: FieldState = FieldState.Empty,
20+
val featuredImage: FieldState = FieldState.Empty,
21+
val sticky: Boolean = false,
22+
val formatLabel: String = "",
23+
val slug: String = "",
24+
val excerpt: String = "",
25+
)
26+
27+
enum class RetryableField {
28+
AUTHOR,
29+
CATEGORIES,
30+
TAGS,
31+
}
32+
33+
sealed interface PostRsSettingsEvent {
34+
data object Finish : PostRsSettingsEvent
35+
data class ShowSnackbar(val message: String) : PostRsSettingsEvent
36+
}

0 commit comments

Comments
 (0)