feat: add newsletter toggle in Settings screen

- Add "Newsletter" section after Content/Tags section
- Switch to enable/disable newsletter features per account
- Info text explaining the toggle's effect on composer
- Best-effort API validation when toggling ON (fetches newsletters count)
- Animated validation status display
This commit is contained in:
Paweł Orzech 2026-03-20 00:44:58 +01:00
parent ed11577be1
commit bbe991b027

View file

@ -32,8 +32,11 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.compose.AsyncImage import coil.compose.AsyncImage
import com.swoosh.microblog.data.AccountManager import com.swoosh.microblog.data.AccountManager
import com.swoosh.microblog.data.NewsletterPreferences
import com.swoosh.microblog.data.SiteMetadataCache import com.swoosh.microblog.data.SiteMetadataCache
import com.swoosh.microblog.data.api.ApiClient import com.swoosh.microblog.data.api.ApiClient
import com.swoosh.microblog.data.repository.PostRepository
import kotlinx.coroutines.launch
import com.swoosh.microblog.data.model.GhostAccount import com.swoosh.microblog.data.model.GhostAccount
import com.swoosh.microblog.ui.animation.SwooshMotion import com.swoosh.microblog.ui.animation.SwooshMotion
import com.swoosh.microblog.ui.components.ConfirmationDialog import com.swoosh.microblog.ui.components.ConfirmationDialog
@ -293,6 +296,13 @@ fun SettingsScreen(
HorizontalDivider() HorizontalDivider()
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
// --- Newsletter section ---
NewsletterSettingsSection()
Spacer(modifier = Modifier.height(24.dp))
HorizontalDivider()
Spacer(modifier = Modifier.height(24.dp))
// --- Current Account section --- // --- Current Account section ---
Text("Current Account", style = MaterialTheme.typography.titleMedium) Text("Current Account", style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
@ -444,6 +454,86 @@ fun SettingsScreen(
} }
} }
@Composable
fun NewsletterSettingsSection() {
val context = LocalContext.current
val newsletterPreferences = remember { NewsletterPreferences(context) }
var newsletterEnabled by remember { mutableStateOf(newsletterPreferences.isNewsletterEnabled()) }
val coroutineScope = rememberCoroutineScope()
var validationStatus by remember { mutableStateOf<String?>(null) }
Text("Newsletter", style = MaterialTheme.typography.titleMedium)
Spacer(modifier = Modifier.height(12.dp))
Card(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
Text(
text = "Enable newsletter features",
style = MaterialTheme.typography.bodyLarge
)
}
Switch(
checked = newsletterEnabled,
onCheckedChange = { enabled ->
newsletterEnabled = enabled
newsletterPreferences.setNewsletterEnabled(enabled)
if (enabled) {
// Best effort: validate by fetching newsletters
validationStatus = "Checking..."
coroutineScope.launch {
try {
val repository = PostRepository(context)
val result = repository.fetchNewsletters()
validationStatus = if (result.isSuccess) {
val count = result.getOrNull()?.size ?: 0
"$count newsletter(s) found"
} else {
null
}
} catch (_: Exception) {
validationStatus = null
}
}
} else {
validationStatus = null
}
}
)
}
Text(
text = "Show newsletter sending options when publishing posts",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
// Validation status
AnimatedVisibility(
visible = validationStatus != null,
enter = fadeIn(SwooshMotion.quick()) + expandVertically(animationSpec = SwooshMotion.snappy()),
exit = fadeOut(SwooshMotion.quick()) + shrinkVertically(animationSpec = SwooshMotion.snappy())
) {
Text(
text = validationStatus ?: "",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary,
modifier = Modifier.padding(top = 4.dp)
)
}
}
}
}
@Composable @Composable
fun ThemeModeSelector( fun ThemeModeSelector(
currentMode: ThemeMode, currentMode: ThemeMode,