feat: add "View results" option to polls that prevents voting after viewing
Adds a "View results" link below poll voting options. Once a user clicks it, the poll results are shown and the user can no longer vote on that poll. The viewed state is persisted in account settings via SharedPreferences. https://claude.ai/code/session_01EkUYT4giQPUvbAJZ54o1se
This commit is contained in:
@@ -131,6 +131,7 @@ private object PrefKeys {
|
|||||||
const val SIGNER_PACKAGE_NAME = "signer_package_name"
|
const val SIGNER_PACKAGE_NAME = "signer_package_name"
|
||||||
const val HAS_DONATED_IN_VERSION = "has_donated_in_version"
|
const val HAS_DONATED_IN_VERSION = "has_donated_in_version"
|
||||||
const val DISMISSED_POLL_NOTE_IDS = "dismissed_poll_note_ids"
|
const val DISMISSED_POLL_NOTE_IDS = "dismissed_poll_note_ids"
|
||||||
|
const val VIEWED_POLL_RESULT_NOTE_IDS = "viewed_poll_result_note_ids"
|
||||||
const val PENDING_ATTESTATIONS = "pending_attestations"
|
const val PENDING_ATTESTATIONS = "pending_attestations"
|
||||||
|
|
||||||
const val ALL_ACCOUNT_INFO = "all_saved_accounts_info"
|
const val ALL_ACCOUNT_INFO = "all_saved_accounts_info"
|
||||||
@@ -391,6 +392,7 @@ object LocalPreferences {
|
|||||||
)
|
)
|
||||||
putStringSet(PrefKeys.HAS_DONATED_IN_VERSION, settings.hasDonatedInVersion.value)
|
putStringSet(PrefKeys.HAS_DONATED_IN_VERSION, settings.hasDonatedInVersion.value)
|
||||||
putStringSet(PrefKeys.DISMISSED_POLL_NOTE_IDS, settings.dismissedPollNoteIds.value)
|
putStringSet(PrefKeys.DISMISSED_POLL_NOTE_IDS, settings.dismissedPollNoteIds.value)
|
||||||
|
putStringSet(PrefKeys.VIEWED_POLL_RESULT_NOTE_IDS, settings.viewedPollResultNoteIds.value)
|
||||||
|
|
||||||
putString(
|
putString(
|
||||||
PrefKeys.PENDING_ATTESTATIONS,
|
PrefKeys.PENDING_ATTESTATIONS,
|
||||||
@@ -476,6 +478,7 @@ object LocalPreferences {
|
|||||||
val hideNIP17WarningDialog = getBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, false)
|
val hideNIP17WarningDialog = getBoolean(PrefKeys.HIDE_NIP_17_WARNING_DIALOG, false)
|
||||||
val hasDonatedInVersion = getStringSet(PrefKeys.HAS_DONATED_IN_VERSION, null) ?: setOf()
|
val hasDonatedInVersion = getStringSet(PrefKeys.HAS_DONATED_IN_VERSION, null) ?: setOf()
|
||||||
val dismissedPollNoteIds = getStringSet(PrefKeys.DISMISSED_POLL_NOTE_IDS, null) ?: setOf()
|
val dismissedPollNoteIds = getStringSet(PrefKeys.DISMISSED_POLL_NOTE_IDS, null) ?: setOf()
|
||||||
|
val viewedPollResultNoteIds = getStringSet(PrefKeys.VIEWED_POLL_RESULT_NOTE_IDS, null) ?: setOf()
|
||||||
val localRelayServers = getStringSet(PrefKeys.LOCAL_RELAY_SERVERS, null) ?: setOf()
|
val localRelayServers = getStringSet(PrefKeys.LOCAL_RELAY_SERVERS, null) ?: setOf()
|
||||||
|
|
||||||
val defaultHomeFollowListStr = getString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, null)
|
val defaultHomeFollowListStr = getString(PrefKeys.DEFAULT_HOME_FOLLOW_LIST, null)
|
||||||
@@ -598,6 +601,7 @@ object LocalPreferences {
|
|||||||
lastReadPerRoute = MutableStateFlow(lastReadPerRoute.await()),
|
lastReadPerRoute = MutableStateFlow(lastReadPerRoute.await()),
|
||||||
hasDonatedInVersion = MutableStateFlow(hasDonatedInVersion),
|
hasDonatedInVersion = MutableStateFlow(hasDonatedInVersion),
|
||||||
dismissedPollNoteIds = MutableStateFlow(dismissedPollNoteIds),
|
dismissedPollNoteIds = MutableStateFlow(dismissedPollNoteIds),
|
||||||
|
viewedPollResultNoteIds = MutableStateFlow(viewedPollResultNoteIds),
|
||||||
pendingAttestations = MutableStateFlow(pendingAttestations.await()),
|
pendingAttestations = MutableStateFlow(pendingAttestations.await()),
|
||||||
backupNipA3PaymentTargets = latestPaymentTargets.await(),
|
backupNipA3PaymentTargets = latestPaymentTargets.await(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2152,6 +2152,10 @@ class Account(
|
|||||||
|
|
||||||
fun dismissPollNotification(noteId: String) = settings.dismissPollNotification(noteId)
|
fun dismissPollNotification(noteId: String) = settings.dismissPollNotification(noteId)
|
||||||
|
|
||||||
|
fun hasViewedPollResults(noteId: String) = settings.hasViewedPollResults(noteId)
|
||||||
|
|
||||||
|
fun markPollResultsViewed(noteId: String) = settings.markPollResultsViewed(noteId)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Log.d("AccountRegisterObservers", "Init")
|
Log.d("AccountRegisterObservers", "Init")
|
||||||
|
|
||||||
|
|||||||
@@ -194,6 +194,7 @@ class AccountSettings(
|
|||||||
val lastReadPerRoute: MutableStateFlow<Map<String, MutableStateFlow<Long>>> = MutableStateFlow(mapOf()),
|
val lastReadPerRoute: MutableStateFlow<Map<String, MutableStateFlow<Long>>> = MutableStateFlow(mapOf()),
|
||||||
val hasDonatedInVersion: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
val hasDonatedInVersion: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
||||||
val dismissedPollNoteIds: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
val dismissedPollNoteIds: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
||||||
|
val viewedPollResultNoteIds: MutableStateFlow<Set<String>> = MutableStateFlow(setOf()),
|
||||||
val pendingAttestations: MutableStateFlow<Map<HexKey, String>> = MutableStateFlow(mapOf()),
|
val pendingAttestations: MutableStateFlow<Map<HexKey, String>> = MutableStateFlow(mapOf()),
|
||||||
var backupNipA3PaymentTargets: PaymentTargetsEvent? = null,
|
var backupNipA3PaymentTargets: PaymentTargetsEvent? = null,
|
||||||
) : EphemeralChatRepository,
|
) : EphemeralChatRepository,
|
||||||
@@ -695,6 +696,21 @@ class AccountSettings(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---
|
||||||
|
// viewed poll results
|
||||||
|
// ---
|
||||||
|
|
||||||
|
fun hasViewedPollResults(noteId: String) = viewedPollResultNoteIds.value.contains(noteId)
|
||||||
|
|
||||||
|
fun markPollResultsViewed(noteId: String) {
|
||||||
|
if (!viewedPollResultNoteIds.value.contains(noteId)) {
|
||||||
|
viewedPollResultNoteIds.update {
|
||||||
|
it + noteId
|
||||||
|
}
|
||||||
|
saveAccountSettings()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
// last read flows
|
// last read flows
|
||||||
// ----
|
// ----
|
||||||
|
|||||||
@@ -265,11 +265,14 @@ fun RenderPollCard(
|
|||||||
|
|
||||||
RenderPollCard(
|
RenderPollCard(
|
||||||
card = card,
|
card = card,
|
||||||
|
noteId = event.id,
|
||||||
onRespond = { responses ->
|
onRespond = { responses ->
|
||||||
accountViewModel.launchSigner {
|
accountViewModel.launchSigner {
|
||||||
accountViewModel.account.pollRespond(event, responses)
|
accountViewModel.account.pollRespond(event, responses)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onViewResults = { accountViewModel.markPollResultsViewed(event.id) },
|
||||||
|
hasViewedResults = { accountViewModel.hasViewedPollResults(event.id) },
|
||||||
resultContent = galleryUser,
|
resultContent = galleryUser,
|
||||||
labelContent = labelContent,
|
labelContent = labelContent,
|
||||||
)
|
)
|
||||||
@@ -278,7 +281,10 @@ fun RenderPollCard(
|
|||||||
@Composable
|
@Composable
|
||||||
fun RenderPollCard(
|
fun RenderPollCard(
|
||||||
card: PollCard,
|
card: PollCard,
|
||||||
|
noteId: String = "",
|
||||||
onRespond: (Set<String>) -> Unit,
|
onRespond: (Set<String>) -> Unit,
|
||||||
|
onViewResults: () -> Unit = {},
|
||||||
|
hasViewedResults: () -> Boolean = { false },
|
||||||
resultContent: @Composable RowScope.(user: User) -> Unit,
|
resultContent: @Composable RowScope.(user: User) -> Unit,
|
||||||
labelContent: @Composable ColumnScope.(code: String, label: String) -> Unit,
|
labelContent: @Composable ColumnScope.(code: String, label: String) -> Unit,
|
||||||
) {
|
) {
|
||||||
@@ -296,12 +302,29 @@ fun RenderPollCard(
|
|||||||
val haveIVoted by card.haveIVotedFlow.collectAsStateWithLifecycle(haveIVoted)
|
val haveIVoted by card.haveIVotedFlow.collectAsStateWithLifecycle(haveIVoted)
|
||||||
if (haveIVoted) {
|
if (haveIVoted) {
|
||||||
RenderResults(card, resultContent, labelContent)
|
RenderResults(card, resultContent, labelContent)
|
||||||
} else if (card.hasEnded()) {
|
} else if (card.hasEnded() || hasViewedResults()) {
|
||||||
RenderResults(card, resultContent, labelContent)
|
RenderResults(card, resultContent, labelContent)
|
||||||
} else {
|
} else {
|
||||||
when (card.type) {
|
var viewingResults by remember { mutableStateOf(false) }
|
||||||
PollType.SINGLE_CHOICE -> RenderSingleChoiceOptions(card, labelContent, onRespond)
|
if (viewingResults) {
|
||||||
PollType.MULTI_CHOICE -> RenderMultiChoiceOptions(card, labelContent, onRespond)
|
RenderResults(card, resultContent, labelContent)
|
||||||
|
} else {
|
||||||
|
when (card.type) {
|
||||||
|
PollType.SINGLE_CHOICE -> RenderSingleChoiceOptions(card, labelContent, onRespond)
|
||||||
|
PollType.MULTI_CHOICE -> RenderMultiChoiceOptions(card, labelContent, onRespond)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.poll_view_results),
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.clickable {
|
||||||
|
onViewResults()
|
||||||
|
viewingResults = true
|
||||||
|
}.padding(vertical = 4.dp),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -615,7 +638,7 @@ fun RenderPollManualPreview() {
|
|||||||
|
|
||||||
ThemeComparisonColumn {
|
ThemeComparisonColumn {
|
||||||
Column(Modifier.padding(10.dp)) {
|
Column(Modifier.padding(10.dp)) {
|
||||||
RenderPollCard(poll, {}, {}) { _, label ->
|
RenderPollCard(poll, onRespond = {}, resultContent = {}) { _, label ->
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
)
|
)
|
||||||
@@ -663,7 +686,7 @@ fun RenderPollManualLongPreview() {
|
|||||||
|
|
||||||
ThemeComparisonColumn {
|
ThemeComparisonColumn {
|
||||||
Column(Modifier.padding(10.dp)) {
|
Column(Modifier.padding(10.dp)) {
|
||||||
RenderPollCard(poll, {}, {}) { _, label ->
|
RenderPollCard(poll, onRespond = {}, resultContent = {}) { _, label ->
|
||||||
Text(
|
Text(
|
||||||
text = label,
|
text = label,
|
||||||
)
|
)
|
||||||
|
|||||||
+4
@@ -1128,6 +1128,10 @@ class AccountViewModel(
|
|||||||
|
|
||||||
fun dismissPollNotification(noteId: String) = account.dismissPollNotification(noteId)
|
fun dismissPollNotification(noteId: String) = account.dismissPollNotification(noteId)
|
||||||
|
|
||||||
|
fun hasViewedPollResults(noteId: String) = account.hasViewedPollResults(noteId)
|
||||||
|
|
||||||
|
fun markPollResultsViewed(noteId: String) = account.markPollResultsViewed(noteId)
|
||||||
|
|
||||||
fun dontTranslateFrom() = account.settings.syncedSettings.languages.dontTranslateFrom.value
|
fun dontTranslateFrom() = account.settings.syncedSettings.languages.dontTranslateFrom.value
|
||||||
|
|
||||||
fun translateTo() = account.settings.syncedSettings.languages.translateTo.value
|
fun translateTo() = account.settings.syncedSettings.languages.translateTo.value
|
||||||
|
|||||||
@@ -1789,6 +1789,7 @@
|
|||||||
<string name="connected">Connected</string>
|
<string name="connected">Connected</string>
|
||||||
<string name="social_proof">Social proof</string>
|
<string name="social_proof">Social proof</string>
|
||||||
<string name="poll_submit">Submit</string>
|
<string name="poll_submit">Submit</string>
|
||||||
|
<string name="poll_view_results">View results</string>
|
||||||
<string name="restart">Restart</string>
|
<string name="restart">Restart</string>
|
||||||
<string name="chess_accept">Accept</string>
|
<string name="chess_accept">Accept</string>
|
||||||
<string name="chess_decline">Decline</string>
|
<string name="chess_decline">Decline</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user