Skip to content

Commit 4cd7086

Browse files
nbradburyclaude
andauthored
RS Posts: Add PostItemState variant UI indicators (#22650)
* RS Posts: Differentiate UI for PostItemState variants Replace isPlaceholder/isError boolean flags in PostRsUiModel with a PostDisplayState enum that distinguishes FetchingWithData (thin progress bar at card bottom) and FailedWithData (warning icon in metadata line) from normal post display. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Simplify toUiModel by passing displayState directly Eliminate intermediate .copy() calls by adding a displayState parameter to the private toUiModel extension function. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * RS Posts: Add Compose previews for all PostDisplayState variants Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 50643b2 commit 4cd7086

3 files changed

Lines changed: 169 additions & 17 deletions

File tree

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

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ data class PostTabUiState(
3838
val isAuthError: Boolean = false
3939
)
4040

41+
enum class PostDisplayState {
42+
NORMAL,
43+
FETCHING_WITH_DATA,
44+
FAILED_WITH_DATA,
45+
PLACEHOLDER,
46+
ERROR
47+
}
48+
4149
data class PostRsUiModel(
4250
val remotePostId: Long,
4351
val title: String,
@@ -53,8 +61,8 @@ data class PostRsUiModel(
5361
val featuredImageId: Long = 0L,
5462
val featuredImageUrl: String? = null,
5563
val actions: List<PostRsMenuAction> = emptyList(),
56-
val isPlaceholder: Boolean = false,
57-
val isError: Boolean = false
64+
val displayState: PostDisplayState =
65+
PostDisplayState.NORMAL
5866
)
5967

6068
enum class PostRsMenuAction(
@@ -108,29 +116,36 @@ fun PostItemState.toUiModel(
108116
is PostItemState.Stale ->
109117
data.toUiModel(showStatus)
110118
is PostItemState.FetchingWithData ->
111-
data.toUiModel(showStatus)
119+
data.toUiModel(
120+
showStatus,
121+
PostDisplayState.FETCHING_WITH_DATA
122+
)
112123
is PostItemState.FailedWithData ->
113-
data.toUiModel(showStatus)
124+
data.toUiModel(
125+
showStatus,
126+
PostDisplayState.FAILED_WITH_DATA
127+
)
114128
is PostItemState.Missing,
115129
is PostItemState.Fetching -> PostRsUiModel(
116130
remotePostId = postId,
117131
title = "",
118132
excerpt = "",
119133
date = "",
120-
isPlaceholder = true
134+
displayState = PostDisplayState.PLACEHOLDER
121135
)
122136
is PostItemState.Failed -> PostRsUiModel(
123137
remotePostId = postId,
124138
title = "",
125139
excerpt = "",
126140
date = "",
127-
isError = true
141+
displayState = PostDisplayState.ERROR
128142
)
129143
}
130144
}
131145

132146
private fun FullEntityAnyPostWithEditContext.toUiModel(
133-
showStatus: Boolean
147+
showStatus: Boolean,
148+
displayState: PostDisplayState = PostDisplayState.NORMAL
134149
): PostRsUiModel {
135150
val post: AnyPostWithEditContext = data
136151
return PostRsUiModel(
@@ -157,7 +172,8 @@ private fun FullEntityAnyPostWithEditContext.toUiModel(
157172
post.status.toLabel()
158173
} else {
159174
0
160-
}
175+
},
176+
displayState = displayState
161177
)
162178
}
163179

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

Lines changed: 144 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import androidx.compose.animation.core.animateFloat
55
import androidx.compose.animation.core.infiniteRepeatable
66
import androidx.compose.animation.core.rememberInfiniteTransition
77
import androidx.compose.animation.core.tween
8+
import androidx.compose.ui.tooling.preview.Preview
89
import androidx.compose.foundation.background
910
import androidx.compose.foundation.clickable
1011
import androidx.compose.foundation.layout.Box
@@ -19,6 +20,8 @@ import androidx.compose.foundation.layout.width
1920
import androidx.compose.foundation.shape.RoundedCornerShape
2021
import androidx.compose.material.icons.Icons
2122
import androidx.compose.material.icons.filled.MoreVert
23+
import androidx.compose.material.icons.filled.Warning
24+
import androidx.compose.material3.LinearProgressIndicator
2225
import androidx.compose.material3.Card
2326
import androidx.compose.material3.CardDefaults
2427
import androidx.compose.material3.DropdownMenu
@@ -44,6 +47,7 @@ import androidx.compose.ui.unit.dp
4447
import coil.compose.AsyncImage
4548
import coil.request.ImageRequest
4649
import org.wordpress.android.R
50+
import org.wordpress.android.ui.postsrs.PostDisplayState
4751
import org.wordpress.android.ui.postsrs.PostRsMenuAction
4852
import org.wordpress.android.ui.postsrs.PostRsUiModel
4953
import org.wordpress.android.ui.postsrs.data.PostRsRestClient
@@ -55,10 +59,17 @@ fun PostRsListItem(
5559
onMenuAction: (PostRsMenuAction) -> Unit,
5660
modifier: Modifier = Modifier
5761
) {
58-
when {
59-
post.isPlaceholder -> PlaceholderItem(modifier)
60-
post.isError -> ErrorItem(modifier)
61-
else -> PostContentItem(post, onClick, onMenuAction, modifier)
62+
when (post.displayState) {
63+
PostDisplayState.PLACEHOLDER ->
64+
PlaceholderItem(modifier)
65+
PostDisplayState.ERROR ->
66+
ErrorItem(modifier)
67+
PostDisplayState.NORMAL,
68+
PostDisplayState.FETCHING_WITH_DATA,
69+
PostDisplayState.FAILED_WITH_DATA ->
70+
PostContentItem(
71+
post, onClick, onMenuAction, modifier
72+
)
6273
}
6374
}
6475

@@ -98,11 +109,42 @@ private fun PostContentItem(
98109
}
99110
}
100111
if (dateText.isNotBlank()) {
101-
Text(
102-
text = dateText,
103-
style = MaterialTheme.typography.labelSmall,
104-
color = MaterialTheme.colorScheme.primary
105-
)
112+
Row(
113+
verticalAlignment =
114+
Alignment.CenterVertically
115+
) {
116+
Text(
117+
text = dateText,
118+
style = MaterialTheme
119+
.typography.labelSmall,
120+
color = MaterialTheme
121+
.colorScheme.primary,
122+
modifier = Modifier.weight(
123+
1f, fill = false
124+
)
125+
)
126+
if (post.displayState ==
127+
PostDisplayState.FAILED_WITH_DATA
128+
) {
129+
Spacer(
130+
modifier =
131+
Modifier.width(4.dp)
132+
)
133+
Icon(
134+
imageVector =
135+
Icons.Default.Warning,
136+
contentDescription =
137+
stringResource(
138+
R.string
139+
.post_rs_sync_failed
140+
),
141+
modifier =
142+
Modifier.size(14.dp),
143+
tint = MaterialTheme
144+
.colorScheme.error
145+
)
146+
}
147+
}
106148
Spacer(modifier = Modifier.height(4.dp))
107149
}
108150
Text(
@@ -157,6 +199,19 @@ private fun PostContentItem(
157199
}
158200
}
159201
}
202+
if (post.displayState ==
203+
PostDisplayState.FETCHING_WITH_DATA
204+
) {
205+
LinearProgressIndicator(
206+
modifier = Modifier
207+
.fillMaxWidth()
208+
.height(2.dp),
209+
color = MaterialTheme.colorScheme.primary
210+
.copy(alpha = 0.5f),
211+
trackColor = MaterialTheme
212+
.colorScheme.surface
213+
)
214+
}
160215
}
161216
}
162217

@@ -283,3 +338,83 @@ private fun ErrorItem(modifier: Modifier = Modifier) {
283338
}
284339

285340
private val FEATURED_IMAGE_SIZE = PostRsRestClient.FEATURED_IMAGE_SIZE_DP.dp
341+
342+
// region Previews
343+
344+
private fun samplePost(
345+
displayState: PostDisplayState = PostDisplayState.NORMAL
346+
) = PostRsUiModel(
347+
remotePostId = 1L,
348+
title = "Welcome to the Flavor Journey",
349+
excerpt = "Exploring the rich world of seasonal "
350+
+ "ingredients and traditional techniques.",
351+
date = "Dec 15, 2025",
352+
authorDisplayName = "Alice",
353+
displayState = displayState
354+
)
355+
356+
@Preview(showBackground = true)
357+
@Composable
358+
private fun PreviewNormal() {
359+
MaterialTheme {
360+
PostRsListItem(
361+
post = samplePost(),
362+
onClick = {},
363+
onMenuAction = {}
364+
)
365+
}
366+
}
367+
368+
@Preview(showBackground = true)
369+
@Composable
370+
private fun PreviewFetchingWithData() {
371+
MaterialTheme {
372+
PostRsListItem(
373+
post = samplePost(
374+
PostDisplayState.FETCHING_WITH_DATA
375+
),
376+
onClick = {},
377+
onMenuAction = {}
378+
)
379+
}
380+
}
381+
382+
@Preview(showBackground = true)
383+
@Composable
384+
private fun PreviewFailedWithData() {
385+
MaterialTheme {
386+
PostRsListItem(
387+
post = samplePost(
388+
PostDisplayState.FAILED_WITH_DATA
389+
),
390+
onClick = {},
391+
onMenuAction = {}
392+
)
393+
}
394+
}
395+
396+
@Preview(showBackground = true)
397+
@Composable
398+
private fun PreviewPlaceholder() {
399+
MaterialTheme {
400+
PostRsListItem(
401+
post = samplePost(PostDisplayState.PLACEHOLDER),
402+
onClick = {},
403+
onMenuAction = {}
404+
)
405+
}
406+
}
407+
408+
@Preview(showBackground = true)
409+
@Composable
410+
private fun PreviewError() {
411+
MaterialTheme {
412+
PostRsListItem(
413+
post = samplePost(PostDisplayState.ERROR),
414+
onClick = {},
415+
onMenuAction = {}
416+
)
417+
}
418+
}
419+
420+
// endregion

WordPress/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,6 +1012,7 @@
10121012
<string name="post_rs_error_update_status">Failed to update post status</string>
10131013
<string name="post_rs_error_load_more">Couldn\'t load more posts</string>
10141014
<string name="post_rs_error_auth">Your authentication has expired. Please sign in again.</string>
1015+
<string name="post_rs_sync_failed">Sync failed</string>
10151016
<string name="post_types_screen_title">Post Types</string>
10161017

10171018
<!-- Debug settings -->

0 commit comments

Comments
 (0)