mirror of
https://github.com/pawelorzech/Swoosh.git
synced 2026-03-31 20:15:41 +00:00
feat: handle email-only posts in PostUploadWorker, Feed, and Detail screens
Phase 4b.3: Sent status in Feed + PostUploadWorker handling. - PostUploadWorker: handle QUEUED_EMAIL_ONLY with email_only=true on GhostPost, pass newsletter slug to repository.createPost() - FeedViewModel: map GhostPost email_only/sent status to FeedPost.emailOnly - FeedScreen FilterChipsBar: add "Sent" chip (magenta, only when newsletter enabled) - FeedScreen PostCardContent: show envelope icon + "Sent" in magenta for sent posts, replace "Share" with "Copy content" for email-only posts - FeedScreen StatusBadge: handle sent/emailOnly status - DetailScreen: show email-only info card with errorContainer color when post is sent via email only, noting it's not visible on the blog
This commit is contained in:
parent
f93a21e743
commit
5c931b138c
4 changed files with 124 additions and 19 deletions
|
|
@ -23,6 +23,7 @@ import androidx.compose.material.icons.automirrored.filled.Article
|
|||
import androidx.compose.material.icons.filled.ContentCopy
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.Email
|
||||
import androidx.compose.material.icons.filled.ExpandLess
|
||||
import androidx.compose.material.icons.filled.ExpandMore
|
||||
import androidx.compose.material.icons.filled.Image
|
||||
|
|
@ -375,6 +376,43 @@ fun DetailScreen(
|
|||
PostStatsSection(post)
|
||||
}
|
||||
}
|
||||
|
||||
// Email-only info card
|
||||
if (post.status == "sent" || post.emailOnly) {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = MaterialTheme.colorScheme.errorContainer
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Email,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onErrorContainer,
|
||||
modifier = Modifier.size(24.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Column {
|
||||
Text(
|
||||
text = "\u2709 SENT VIA EMAIL ONLY",
|
||||
style = MaterialTheme.typography.labelLarge,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer
|
||||
)
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = "This post is not visible on your blog.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onErrorContainer.copy(alpha = 0.8f)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -767,6 +767,12 @@ fun FilterChipsBar(
|
|||
activeFilter: PostFilter,
|
||||
onFilterSelected: (PostFilter) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val newsletterEnabled = remember {
|
||||
com.swoosh.microblog.data.NewsletterPreferences(context).isNewsletterEnabled()
|
||||
}
|
||||
val sentColor = Color(0xFF6A1B9A) // magenta
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
|
@ -775,19 +781,29 @@ fun FilterChipsBar(
|
|||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
PostFilter.values().forEach { filter ->
|
||||
// Only show SENT chip when newsletter is enabled
|
||||
if (filter == PostFilter.SENT && !newsletterEnabled) return@forEach
|
||||
|
||||
val selected = filter == activeFilter
|
||||
val containerColor by animateColorAsState(
|
||||
targetValue = if (selected)
|
||||
MaterialTheme.colorScheme.primaryContainer
|
||||
else
|
||||
MaterialTheme.colorScheme.surface,
|
||||
targetValue = when {
|
||||
selected && filter == PostFilter.SENT -> sentColor.copy(alpha = 0.2f)
|
||||
selected -> MaterialTheme.colorScheme.primaryContainer
|
||||
else -> MaterialTheme.colorScheme.surface
|
||||
},
|
||||
animationSpec = SwooshMotion.quick(),
|
||||
label = "chipColor"
|
||||
)
|
||||
FilterChip(
|
||||
selected = selected,
|
||||
onClick = { onFilterSelected(filter) },
|
||||
label = { Text(filter.displayName) },
|
||||
label = {
|
||||
Text(
|
||||
filter.displayName,
|
||||
color = if (selected && filter == PostFilter.SENT) sentColor
|
||||
else Color.Unspecified
|
||||
)
|
||||
},
|
||||
colors = FilterChipDefaults.filterChipColors(
|
||||
selectedContainerColor = containerColor
|
||||
)
|
||||
|
|
@ -1647,12 +1663,15 @@ fun PostCardContent(
|
|||
val stats = remember(post.textContent, post.imageUrl, post.linkUrl) {
|
||||
PostStats.fromFeedPost(post)
|
||||
}
|
||||
val isSent = post.status == "sent" || post.emailOnly
|
||||
val statusLabel = when {
|
||||
post.queueStatus != QueueStatus.NONE -> "Pending"
|
||||
isSent -> "Sent"
|
||||
else -> post.status.replaceFirstChar { it.uppercase() }
|
||||
}
|
||||
val statusColor = when {
|
||||
post.queueStatus != QueueStatus.NONE -> Color(0xFFE65100)
|
||||
isSent -> Color(0xFF6A1B9A)
|
||||
post.status == "published" -> Color(0xFF2E7D32)
|
||||
post.status == "scheduled" -> Color(0xFF1565C0)
|
||||
else -> Color(0xFF7B1FA2)
|
||||
|
|
@ -1661,19 +1680,28 @@ fun PostCardContent(
|
|||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||
) {
|
||||
if (isSent) {
|
||||
Icon(
|
||||
Icons.Default.Email,
|
||||
contentDescription = "Sent",
|
||||
modifier = Modifier.size(12.dp),
|
||||
tint = statusColor
|
||||
)
|
||||
} else {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(8.dp)
|
||||
.clip(CircleShape)
|
||||
.background(statusColor)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = statusLabel,
|
||||
style = MaterialTheme.typography.labelSmall.copy(fontWeight = FontWeight.Bold),
|
||||
color = statusColor
|
||||
)
|
||||
Text(
|
||||
text = "·",
|
||||
text = "\u00B7",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
|
|
@ -1714,8 +1742,35 @@ fun PostCardContent(
|
|||
)
|
||||
}
|
||||
|
||||
// Share action (copies link to clipboard)
|
||||
if (isPublished && hasShareableUrl) {
|
||||
// Share / Copy content action
|
||||
if (isSent) {
|
||||
// For sent (email-only) posts, show "Copy content" instead of "Share"
|
||||
val copyContext = LocalContext.current
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.clickable {
|
||||
val clipboard = copyContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
clipboard.setPrimaryClip(ClipData.newPlainText("Post content", post.textContent))
|
||||
snackbarHostState?.let { host ->
|
||||
coroutineScope.launch {
|
||||
host.showSnackbar("Content copied to clipboard")
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.ContentCopy,
|
||||
contentDescription = "Copy content",
|
||||
modifier = Modifier.size(24.dp),
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
Text(
|
||||
text = "Copy",
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
} else if (isPublished && hasShareableUrl) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
modifier = Modifier.clickable {
|
||||
|
|
@ -2168,8 +2223,10 @@ fun buildHighlightedString(
|
|||
|
||||
@Composable
|
||||
fun StatusBadge(post: FeedPost) {
|
||||
val isSent = post.status == "sent" || post.emailOnly
|
||||
val (label, containerColor, labelColor) = when {
|
||||
post.queueStatus != QueueStatus.NONE -> Triple("Pending", Color(0xFFFFF3E0), Color(0xFFE65100))
|
||||
isSent -> Triple("Sent", Color(0xFFF3E5F5), Color(0xFF6A1B9A))
|
||||
post.status == "published" -> Triple("Published", Color(0xFFE8F5E9), Color(0xFF2E7D32))
|
||||
post.status == "scheduled" -> Triple("Scheduled", Color(0xFFE3F2FD), Color(0xFF1565C0))
|
||||
else -> Triple("Draft", Color(0xFFF3E5F5), Color(0xFF7B1FA2))
|
||||
|
|
|
|||
|
|
@ -536,6 +536,7 @@ class FeedViewModel(application: Application) : AndroidViewModel(application) {
|
|||
}
|
||||
}
|
||||
val fileData = extractFileCardFromMobiledoc(mobiledoc)
|
||||
val isEmailOnly = status == "sent" || email_only == true
|
||||
return FeedPost(
|
||||
ghostId = id,
|
||||
slug = slug,
|
||||
|
|
@ -558,7 +559,8 @@ class FeedViewModel(application: Application) : AndroidViewModel(application) {
|
|||
updatedAt = updated_at,
|
||||
isLocal = false,
|
||||
fileUrl = fileData?.first,
|
||||
fileName = fileData?.second
|
||||
fileName = fileData?.second,
|
||||
emailOnly = isEmailOnly
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -645,7 +647,8 @@ class FeedViewModel(application: Application) : AndroidViewModel(application) {
|
|||
isLocal = true,
|
||||
queueStatus = queueStatus,
|
||||
fileUrl = uploadedFileUrl ?: fileUri,
|
||||
fileName = fileName
|
||||
fileName = fileName,
|
||||
emailOnly = emailOnly
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,12 +94,15 @@ class PostUploadWorker(
|
|||
}
|
||||
val ghostTags = tagNames.map { GhostTag(name = it) }
|
||||
|
||||
val isEmailOnly = post.queueStatus == QueueStatus.QUEUED_EMAIL_ONLY
|
||||
|
||||
val ghostPost = GhostPost(
|
||||
title = post.title,
|
||||
mobiledoc = mobiledoc,
|
||||
status = when (post.queueStatus) {
|
||||
QueueStatus.QUEUED_PUBLISH -> "published"
|
||||
QueueStatus.QUEUED_SCHEDULED -> "scheduled"
|
||||
QueueStatus.QUEUED_EMAIL_ONLY -> "published"
|
||||
else -> "draft"
|
||||
},
|
||||
featured = post.featured,
|
||||
|
|
@ -107,13 +110,17 @@ class PostUploadWorker(
|
|||
feature_image_alt = post.imageAlt,
|
||||
published_at = post.scheduledAt,
|
||||
visibility = "public",
|
||||
tags = ghostTags.ifEmpty { null }
|
||||
tags = ghostTags.ifEmpty { null },
|
||||
email_only = if (isEmailOnly) true else null
|
||||
)
|
||||
|
||||
// Determine newsletter slug for email-only or newsletter posts
|
||||
val newsletterSlug = post.newsletterSlug
|
||||
|
||||
val result = if (post.ghostId != null) {
|
||||
repository.updatePost(post.ghostId, ghostPost)
|
||||
repository.updatePost(post.ghostId, ghostPost, newsletter = newsletterSlug)
|
||||
} else {
|
||||
repository.createPost(ghostPost)
|
||||
repository.createPost(ghostPost, newsletter = newsletterSlug)
|
||||
}
|
||||
|
||||
result.fold(
|
||||
|
|
|
|||
Loading…
Reference in a new issue