feat: consolidate poll creation into a single screen with poll type selector

Replace the two-button expandable FAB (Regular Poll / Zap Poll) with a
single FAB navigating to one unified poll screen. The poll type (Regular
vs Zap) is now selected via FilterChip options within the post form,
eliminating the Route.NewZapPoll route entirely.

https://claude.ai/code/session_01PqBSYZvCLBBUCifUFRoLmD
This commit is contained in:
Claude
2026-04-02 17:44:05 +00:00
parent 3d91fb75d4
commit 10f617550b
5 changed files with 55 additions and 179 deletions
@@ -412,17 +412,6 @@ fun BuildNavigation(
composableFromBottomArgs<Route.NewPoll> {
PollPostScreen(
isZapPoll = false,
message = it.message,
draftId = it.draft,
accountViewModel = accountViewModel,
nav = nav,
)
}
composableFromBottomArgs<Route.NewZapPoll> {
PollPostScreen(
isZapPoll = true,
message = it.message,
draftId = it.draft,
accountViewModel = accountViewModel,
@@ -373,12 +373,6 @@ sealed class Route {
val draft: String? = null,
) : Route()
@Serializable
data class NewZapPoll(
val message: String? = null,
val draft: String? = null,
) : Route()
@Serializable
data class VoiceReply(
val replyToNoteId: String,
@@ -26,6 +26,7 @@ import android.net.Uri
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.clickable
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@@ -42,12 +43,14 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Poll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
@@ -108,7 +111,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.settings.SettingsRow
import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size10dp
import com.vitorpamplona.amethyst.ui.theme.Size19Modifier
import com.vitorpamplona.amethyst.ui.theme.Size30Modifier
import com.vitorpamplona.amethyst.ui.theme.Size35Modifier
import com.vitorpamplona.amethyst.ui.theme.Size35dp
@@ -334,21 +336,39 @@ private fun NewPostScreenBody(
}
}
if (postViewModel.wantsPoll) {
Row(
verticalAlignment = CenterVertically,
if (postViewModel.wantsPoll || postViewModel.wantsZapPoll) {
Column(
modifier = Modifier.padding(vertical = Size10dp, horizontal = Size10dp),
) {
PollOptionsField(postViewModel)
}
}
if (postViewModel.wantsZapPoll) {
Row(
verticalAlignment = CenterVertically,
modifier = Modifier.padding(vertical = Size10dp, horizontal = Size10dp),
) {
ZapPollField(postViewModel)
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
FilterChip(
selected = postViewModel.wantsPoll,
onClick = {
postViewModel.wantsPoll = true
postViewModel.wantsZapPoll = false
},
label = { Text(stringRes(R.string.kind_poll)) },
)
FilterChip(
selected = postViewModel.wantsZapPoll,
onClick = {
postViewModel.wantsZapPoll = true
postViewModel.wantsPoll = false
},
label = { Text(stringRes(R.string.kind_zap_poll)) },
)
}
if (postViewModel.wantsPoll) {
Row(verticalAlignment = CenterVertically) {
PollOptionsField(postViewModel)
}
} else {
Row(verticalAlignment = CenterVertically) {
ZapPollField(postViewModel)
}
}
}
}
@@ -588,24 +608,14 @@ private fun BottomRowActions(postViewModel: ShortNotePostViewModel) {
maxDurationSeconds = MAX_VOICE_RECORD_SECONDS,
)
if (postViewModel.canUsePoll) {
AddPollButton(postViewModel.wantsPoll) {
postViewModel.wantsPoll = !postViewModel.wantsPoll
if (postViewModel.wantsPoll) {
if (postViewModel.wantsZapPoll) {
postViewModel.wantsZapPoll = false
}
}
}
}
if (postViewModel.canUseZapPoll) {
AddZapPollButton(postViewModel.wantsZapPoll) {
postViewModel.wantsZapPoll = !postViewModel.wantsZapPoll
if (postViewModel.wantsZapPoll) {
if (postViewModel.wantsPoll) {
postViewModel.wantsPoll = false
}
if (postViewModel.canUsePoll || postViewModel.canUseZapPoll) {
AddPollButton(postViewModel.wantsPoll || postViewModel.wantsZapPoll) {
val isActive = postViewModel.wantsPoll || postViewModel.wantsZapPoll
if (isActive) {
postViewModel.wantsPoll = false
postViewModel.wantsZapPoll = false
} else {
postViewModel.wantsPoll = true
}
}
}
@@ -655,32 +665,6 @@ private fun BottomRowActionsPreview() {
}
}
@Composable
private fun AddZapPollButton(
isPollActive: Boolean,
onClick: () -> Unit,
) {
IconButton(
onClick = { onClick() },
) {
if (!isPollActive) {
Icon(
painter = painterRes(R.drawable.ic_poll, 1),
contentDescription = stringRes(id = R.string.poll),
modifier = Size19Modifier,
tint = MaterialTheme.colorScheme.onBackground,
)
} else {
Icon(
painter = painterRes(R.drawable.ic_poll, 1),
contentDescription = stringRes(id = R.string.disable_poll),
modifier = Size19Modifier,
tint = MaterialTheme.colorScheme.primary,
)
}
}
}
@Composable
private fun AddPollButton(
isPollActive: Boolean,
@@ -20,29 +20,12 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.polls
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Poll
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
@@ -53,86 +36,17 @@ import com.vitorpamplona.amethyst.ui.theme.Size55Modifier
@Composable
fun NewPollButton(nav: INav) {
var isOpen by remember { mutableStateOf(false) }
Column {
AnimatedVisibility(
visible = isOpen,
enter = slideInVertically(initialOffsetY = { it / 2 }) + fadeIn(),
exit = slideOutVertically(targetOffsetY = { it / 2 }) + fadeOut(),
) {
Column {
FloatingActionButton(
onClick = {
nav.nav(Route.NewPoll())
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
imageVector = Icons.Outlined.Poll,
contentDescription = stringRes(id = R.string.new_regular_poll),
modifier = Size26Modifier,
tint = Color.White,
)
}
Spacer(modifier = Modifier.height(20.dp))
FloatingActionButton(
onClick = {
nav.nav(Route.NewZapPoll())
isOpen = false
},
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
painter = painterRes(R.drawable.ic_poll, 1),
contentDescription = stringRes(id = R.string.new_zap_poll),
modifier = Size26Modifier,
tint = Color.White,
)
}
Spacer(modifier = Modifier.height(20.dp))
}
}
FloatingActionButton(
onClick = { isOpen = !isOpen },
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
AnimatedVisibility(
visible = isOpen,
enter = fadeIn(),
exit = fadeOut(),
) {
Icon(
imageVector = Icons.Outlined.Close,
contentDescription = stringRes(id = R.string.new_poll),
modifier = Size26Modifier,
tint = Color.White,
)
}
AnimatedVisibility(
visible = !isOpen,
enter = fadeIn(),
exit = fadeOut(),
) {
Icon(
painter = painterRes(R.drawable.ic_compose, 4),
contentDescription = stringRes(id = R.string.new_poll),
modifier = Size26Modifier,
tint = Color.White,
)
}
}
FloatingActionButton(
onClick = { nav.nav(Route.NewPoll()) },
modifier = Size55Modifier,
shape = CircleShape,
containerColor = MaterialTheme.colorScheme.primary,
) {
Icon(
painter = painterRes(R.drawable.ic_compose, 4),
contentDescription = stringRes(id = R.string.new_poll),
modifier = Size26Modifier,
tint = Color.White,
)
}
}
@@ -35,7 +35,6 @@ import kotlinx.coroutines.FlowPreview
@OptIn(ExperimentalMaterial3Api::class, FlowPreview::class)
@Composable
fun PollPostScreen(
isZapPoll: Boolean,
message: String? = null,
draftId: HexKey? = null,
accountViewModel: AccountViewModel,
@@ -51,11 +50,7 @@ fun PollPostScreen(
postViewModel.message.setTextAndPlaceCursorAtEnd(it)
postViewModel.onMessageChanged()
}
if (isZapPoll) {
postViewModel.wantsZapPoll = true
} else {
postViewModel.wantsPoll = true
}
postViewModel.wantsPoll = true
}
NewPostScreenInner(postViewModel, accountViewModel, nav)