mirror of
https://github.com/pawelorzech/Swoosh.git
synced 2026-03-31 11:55:47 +00:00
refactor: extract ConfirmationDialog, fix animation efficiency issues
This commit is contained in:
parent
15c678556e
commit
2470f9a049
4 changed files with 132 additions and 132 deletions
|
|
@ -0,0 +1,40 @@
|
|||
package com.swoosh.microblog.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun ConfirmationDialog(
|
||||
title: String,
|
||||
message: String,
|
||||
confirmLabel: String,
|
||||
onConfirm: () -> Unit,
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
AnimatedDialog(onDismissRequest = onDismiss) {
|
||||
Card(modifier = Modifier.padding(horizontal = 24.dp)) {
|
||||
Column(modifier = Modifier.padding(24.dp)) {
|
||||
Text(title, style = MaterialTheme.typography.headlineSmall)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(message)
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = onDismiss) { Text("Cancel") }
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Button(
|
||||
onClick = onConfirm,
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.error
|
||||
)
|
||||
) { Text(confirmLabel) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ 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.components.ConfirmationDialog
|
||||
import com.swoosh.microblog.ui.feed.FullScreenGallery
|
||||
import com.swoosh.microblog.ui.feed.StatusBadge
|
||||
import com.swoosh.microblog.ui.feed.formatRelativeTime
|
||||
|
|
@ -363,30 +363,16 @@ fun DetailScreen(
|
|||
|
||||
// D3: Animated delete dialog
|
||||
if (showDeleteDialog) {
|
||||
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.buttonColors(containerColor = MaterialTheme.colorScheme.error)
|
||||
) { Text("Delete") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfirmationDialog(
|
||||
title = "Delete Post",
|
||||
message = "Are you sure you want to delete this post? This action cannot be undone.",
|
||||
confirmLabel = "Delete",
|
||||
onConfirm = {
|
||||
showDeleteDialog = false
|
||||
onDelete(post)
|
||||
},
|
||||
onDismiss = { showDeleteDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
// Full-screen gallery
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ import com.swoosh.microblog.data.model.PostFilter
|
|||
import com.swoosh.microblog.data.model.PostStats
|
||||
import com.swoosh.microblog.data.model.QueueStatus
|
||||
import com.swoosh.microblog.data.model.SortOrder
|
||||
import com.swoosh.microblog.ui.components.ConfirmationDialog
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
|
|
@ -661,6 +662,7 @@ fun FeedScreen(
|
|||
if (state.posts.isNotEmpty() && !initialLoadComplete) {
|
||||
delay(SwooshMotion.StaggerDelayMs * minOf(state.posts.size, 8) + 300)
|
||||
initialLoadComplete = true
|
||||
animatedKeys.clear() // Free memory — no longer needed
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -709,25 +711,16 @@ fun FeedScreen(
|
|||
|
||||
// Delete confirmation dialog
|
||||
if (postPendingDelete != null) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { postPendingDelete = null },
|
||||
title = { Text("Delete this post?") },
|
||||
text = { Text("This action cannot be undone.") },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
val post = postPendingDelete!!
|
||||
postPendingDelete = null
|
||||
viewModel.deletePostWithUndo(post)
|
||||
},
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
)
|
||||
) { Text("Delete") }
|
||||
ConfirmationDialog(
|
||||
title = "Delete this post?",
|
||||
message = "This action cannot be undone.",
|
||||
confirmLabel = "Delete",
|
||||
onConfirm = {
|
||||
val post = postPendingDelete!!
|
||||
postPendingDelete = null
|
||||
viewModel.deletePostWithUndo(post)
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { postPendingDelete = null }) { Text("Cancel") }
|
||||
}
|
||||
onDismiss = { postPendingDelete = null }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -756,27 +749,16 @@ fun FeedScreen(
|
|||
|
||||
// Account delete confirmation dialog
|
||||
showDeleteConfirmation?.let { account ->
|
||||
AlertDialog(
|
||||
onDismissRequest = { showDeleteConfirmation = null },
|
||||
title = { Text("Remove Account") },
|
||||
text = { Text("Remove \"${account.name}\"? Local drafts for this account will be kept.") },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
viewModel.removeAccount(account.id)
|
||||
showDeleteConfirmation = null
|
||||
if (accounts.size <= 1) {
|
||||
showAccountSwitcher = false
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
)
|
||||
) { Text("Remove") }
|
||||
ConfirmationDialog(
|
||||
title = "Remove Account",
|
||||
message = "Remove \"${account.name}\"? Local drafts for this account will be kept.",
|
||||
confirmLabel = "Remove",
|
||||
onConfirm = {
|
||||
viewModel.removeAccount(account.id)
|
||||
showDeleteConfirmation = null
|
||||
if (accounts.size <= 1) showAccountSwitcher = false
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = { showDeleteConfirmation = null }) { Text("Cancel") }
|
||||
}
|
||||
onDismiss = { showDeleteConfirmation = null }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -1127,7 +1109,7 @@ fun AccountSwitcherBottomSheet(
|
|||
|
||||
accounts.forEachIndexed { index, account ->
|
||||
var itemVisible by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) {
|
||||
LaunchedEffect(account.id) {
|
||||
delay(SwooshMotion.StaggerDelayMs * index)
|
||||
itemVisible = true
|
||||
}
|
||||
|
|
@ -1395,6 +1377,37 @@ fun RecentSearchesList(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PulsingAssistChip(
|
||||
label: String,
|
||||
isUploading: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
if (isUploading) {
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "queuePulse")
|
||||
val chipAlpha by infiniteTransition.animateFloat(
|
||||
initialValue = 0.6f,
|
||||
targetValue = 1f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(600),
|
||||
repeatMode = RepeatMode.Reverse
|
||||
),
|
||||
label = "uploadPulse"
|
||||
)
|
||||
AssistChip(
|
||||
onClick = {},
|
||||
label = { Text(label, style = MaterialTheme.typography.labelSmall) },
|
||||
modifier = modifier.graphicsLayer { alpha = chipAlpha }
|
||||
)
|
||||
} else {
|
||||
AssistChip(
|
||||
onClick = {},
|
||||
label = { Text(label, style = MaterialTheme.typography.labelSmall) },
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun PostCardContent(
|
||||
|
|
@ -1617,21 +1630,10 @@ fun PostCardContent(
|
|||
else -> ""
|
||||
}
|
||||
val isUploading = post.queueStatus == QueueStatus.UPLOADING
|
||||
val infiniteTransition = rememberInfiniteTransition(label = "queuePulse")
|
||||
val chipAlpha by infiniteTransition.animateFloat(
|
||||
initialValue = if (isUploading) 0.6f else 1f,
|
||||
targetValue = 1f,
|
||||
animationSpec = infiniteRepeatable(
|
||||
animation = tween(600),
|
||||
repeatMode = RepeatMode.Reverse
|
||||
),
|
||||
label = "uploadPulse"
|
||||
)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
AssistChip(
|
||||
onClick = {},
|
||||
label = { Text(queueLabel, style = MaterialTheme.typography.labelSmall) },
|
||||
modifier = Modifier.graphicsLayer { alpha = if (isUploading) chipAlpha else 1f }
|
||||
PulsingAssistChip(
|
||||
label = queueLabel,
|
||||
isUploading = isUploading
|
||||
)
|
||||
if (post.queueStatus == QueueStatus.QUEUED_PUBLISH || post.queueStatus == QueueStatus.QUEUED_SCHEDULED) {
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.swoosh.microblog.data.AccountManager
|
||||
import com.swoosh.microblog.data.api.ApiClient
|
||||
import com.swoosh.microblog.ui.animation.SwooshMotion
|
||||
import com.swoosh.microblog.ui.components.AnimatedDialog
|
||||
import com.swoosh.microblog.ui.components.ConfirmationDialog
|
||||
import com.swoosh.microblog.ui.feed.AccountAvatar
|
||||
import com.swoosh.microblog.ui.theme.ThemeMode
|
||||
import com.swoosh.microblog.ui.theme.ThemeViewModel
|
||||
|
|
@ -177,63 +177,35 @@ fun SettingsScreen(
|
|||
}
|
||||
|
||||
if (showDisconnectDialog) {
|
||||
AnimatedDialog(onDismissRequest = { showDisconnectDialog = false }) {
|
||||
Card(modifier = Modifier.padding(horizontal = 24.dp)) {
|
||||
Column(modifier = Modifier.padding(24.dp)) {
|
||||
Text("Disconnect Account?", style = MaterialTheme.typography.headlineSmall)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text("Remove \"${activeAccount?.name ?: ""}\"? You'll need to set up again.")
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = { showDisconnectDialog = false }) { Text("Cancel") }
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
showDisconnectDialog = false
|
||||
activeAccount?.let { account ->
|
||||
accountManager.removeAccount(account.id)
|
||||
ApiClient.reset()
|
||||
if (accountManager.getAccounts().isEmpty()) onLogout() else onBack()
|
||||
}
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)
|
||||
) { Text("Disconnect") }
|
||||
}
|
||||
ConfirmationDialog(
|
||||
title = "Disconnect Account?",
|
||||
message = "Remove \"${activeAccount?.name ?: ""}\"? You'll need to set up again.",
|
||||
confirmLabel = "Disconnect",
|
||||
onConfirm = {
|
||||
showDisconnectDialog = false
|
||||
activeAccount?.let { account ->
|
||||
accountManager.removeAccount(account.id)
|
||||
ApiClient.reset()
|
||||
if (accountManager.getAccounts().isEmpty()) onLogout() else onBack()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onDismiss = { showDisconnectDialog = false }
|
||||
)
|
||||
}
|
||||
|
||||
if (showDisconnectAllDialog) {
|
||||
AnimatedDialog(onDismissRequest = { showDisconnectAllDialog = false }) {
|
||||
Card(modifier = Modifier.padding(horizontal = 24.dp)) {
|
||||
Column(modifier = Modifier.padding(24.dp)) {
|
||||
Text("Disconnect All?", style = MaterialTheme.typography.headlineSmall)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text("Remove all accounts? You'll need to set up from scratch.")
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
TextButton(onClick = { showDisconnectAllDialog = false }) { Text("Cancel") }
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
Button(
|
||||
onClick = {
|
||||
showDisconnectAllDialog = false
|
||||
accountManager.clearAll()
|
||||
ApiClient.reset()
|
||||
onLogout()
|
||||
},
|
||||
colors = ButtonDefaults.buttonColors(containerColor = MaterialTheme.colorScheme.error)
|
||||
) { Text("Disconnect All") }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ConfirmationDialog(
|
||||
title = "Disconnect All?",
|
||||
message = "Remove all accounts? You'll need to set up from scratch.",
|
||||
confirmLabel = "Disconnect All",
|
||||
onConfirm = {
|
||||
showDisconnectAllDialog = false
|
||||
accountManager.clearAll()
|
||||
ApiClient.reset()
|
||||
onLogout()
|
||||
},
|
||||
onDismiss = { showDisconnectAllDialog = false }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue