mirror of
https://github.com/pawelorzech/Swoosh.git
synced 2026-03-31 20:15:41 +00:00
feat: add content reveal and animated delete dialog in detail
This commit is contained in:
parent
5183862533
commit
4a7005ce1e
1 changed files with 170 additions and 112 deletions
|
|
@ -6,7 +6,12 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.scaleIn
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
|
|
@ -47,9 +52,12 @@ import com.swoosh.microblog.data.model.FeedPost
|
|||
import com.swoosh.microblog.data.model.LinkPreview
|
||||
import com.swoosh.microblog.data.model.PostStats
|
||||
import com.swoosh.microblog.data.model.QueueStatus
|
||||
import com.swoosh.microblog.ui.animation.SwooshMotion
|
||||
import com.swoosh.microblog.ui.components.AnimatedDialog
|
||||
import com.swoosh.microblog.ui.feed.FullScreenGallery
|
||||
import com.swoosh.microblog.ui.feed.StatusBadge
|
||||
import com.swoosh.microblog.ui.feed.formatRelativeTime
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
|
|
@ -85,6 +93,16 @@ fun DetailScreen(
|
|||
emptyList()
|
||||
}
|
||||
|
||||
// D1: Content reveal sequence
|
||||
val revealCount = 6 // status, text, tags, gallery, link, stats
|
||||
val sectionVisible = remember { List(revealCount) { mutableStateOf(false) } }
|
||||
LaunchedEffect(Unit) {
|
||||
sectionVisible.forEachIndexed { index, state ->
|
||||
delay(SwooshMotion.RevealDelayMs * index)
|
||||
state.value = true
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
|
|
@ -195,7 +213,11 @@ fun DetailScreen(
|
|||
.verticalScroll(rememberScrollState())
|
||||
.padding(16.dp)
|
||||
) {
|
||||
// Status and time
|
||||
// Section 0 — Status and time
|
||||
AnimatedVisibility(
|
||||
visible = sectionVisible[0].value,
|
||||
enter = fadeIn(SwooshMotion.quick()) + scaleIn(initialScale = 0.8f, animationSpec = SwooshMotion.bouncy())
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
|
|
@ -207,17 +229,27 @@ fun DetailScreen(
|
|||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
// Full text content
|
||||
// Section 1 — Full text content
|
||||
AnimatedVisibility(
|
||||
visible = sectionVisible[1].value,
|
||||
enter = fadeIn(SwooshMotion.quick()) + slideInVertically(initialOffsetY = { 20 }, animationSpec = SwooshMotion.gentle())
|
||||
) {
|
||||
Text(
|
||||
text = post.textContent,
|
||||
style = MaterialTheme.typography.bodyLarge
|
||||
)
|
||||
}
|
||||
|
||||
// Tags
|
||||
if (post.tags.isNotEmpty()) {
|
||||
// Section 2 — Tags
|
||||
AnimatedVisibility(
|
||||
visible = sectionVisible[2].value && post.tags.isNotEmpty(),
|
||||
enter = fadeIn(SwooshMotion.quick()) + slideInVertically(initialOffsetY = { 20 }, animationSpec = SwooshMotion.gentle())
|
||||
) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
|
|
@ -238,9 +270,14 @@ fun DetailScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Image gallery
|
||||
if (allImages.isNotEmpty()) {
|
||||
// Section 3 — Image gallery
|
||||
AnimatedVisibility(
|
||||
visible = sectionVisible[3].value && allImages.isNotEmpty(),
|
||||
enter = fadeIn(SwooshMotion.quick()) + slideInVertically(initialOffsetY = { 20 }, animationSpec = SwooshMotion.gentle())
|
||||
) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
DetailImageGallery(
|
||||
images = allImages,
|
||||
|
|
@ -261,9 +298,14 @@ fun DetailScreen(
|
|||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Link preview
|
||||
if (post.linkUrl != null) {
|
||||
// Section 4 — Link preview
|
||||
AnimatedVisibility(
|
||||
visible = sectionVisible[4].value && post.linkUrl != null,
|
||||
enter = fadeIn(SwooshMotion.quick()) + slideInVertically(initialOffsetY = { 20 }, animationSpec = SwooshMotion.gentle())
|
||||
) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
OutlinedCard(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(modifier = Modifier.padding(12.dp)) {
|
||||
|
|
@ -294,6 +336,7 @@ fun DetailScreen(
|
|||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
if (post.linkUrl != null) {
|
||||
Text(
|
||||
text = post.linkUrl,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
|
|
@ -302,33 +345,48 @@ fun DetailScreen(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stats section
|
||||
// Section 5 — PostStatsSection
|
||||
AnimatedVisibility(
|
||||
visible = sectionVisible[5].value,
|
||||
enter = slideInVertically(initialOffsetY = { it / 4 }, animationSpec = SwooshMotion.gentle()) + fadeIn(SwooshMotion.quick())
|
||||
) {
|
||||
Column {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
PostStatsSection(post)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// D3: Animated delete dialog
|
||||
if (showDeleteDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteDialog = false },
|
||||
title = { Text("Delete Post") },
|
||||
text = { Text("Are you sure you want to delete this post? This action cannot be undone.") },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
AnimatedDialog(onDismissRequest = { showDeleteDialog = false }) {
|
||||
Card(modifier = Modifier.padding(horizontal = 24.dp)) {
|
||||
Column(modifier = Modifier.padding(24.dp)) {
|
||||
Text("Delete Post", style = MaterialTheme.typography.headlineSmall)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text("Are you sure you want to delete this post? This action cannot be undone.")
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = { showDeleteDialog = false }) { Text("Cancel") }
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
showDeleteDialog = false
|
||||
onDelete(post)
|
||||
},
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
)
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)
|
||||
) { Text("Delete") }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDeleteDialog = false }) { Text("Cancel") }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Full-screen gallery
|
||||
|
|
|
|||
Loading…
Reference in a new issue