Brings the creation of Zap Polls back.
This commit is contained in:
@@ -58,7 +58,7 @@ import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryE
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.relationshipStatus.ContactCardEvent
|
||||
import com.vitorpamplona.quartz.experimental.trustedAssertions.list.TrustProviderListEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
@@ -961,7 +961,7 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
@Suppress("DEPRECATION")
|
||||
fun computeReplyTo(event: Event): List<Note> =
|
||||
when (event) {
|
||||
is PollNoteEvent -> {
|
||||
is ZapPollEvent -> {
|
||||
event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
|
||||
}
|
||||
|
||||
@@ -1059,7 +1059,7 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
}
|
||||
|
||||
fun consume(
|
||||
event: PollNoteEvent,
|
||||
event: ZapPollEvent,
|
||||
relay: NormalizedRelayUrl?,
|
||||
wasVerified: Boolean,
|
||||
) = consumeRegularEvent(event, relay, wasVerified)
|
||||
@@ -3141,7 +3141,7 @@ object LocalCache : ILocalCache, ICacheProvider {
|
||||
is PublicMessageEvent -> consume(event, relay, wasVerified)
|
||||
is PeopleListEvent -> consume(event, relay, wasVerified)
|
||||
is CodeSnippetEvent -> consume(event, relay, wasVerified)
|
||||
is PollNoteEvent -> consume(event, relay, wasVerified)
|
||||
is ZapPollEvent -> consume(event, relay, wasVerified)
|
||||
is PollEvent -> consume(event, relay, wasVerified)
|
||||
is PollResponseEvent -> consume(event, relay, wasVerified)
|
||||
is ReactionEvent -> consume(event, relay, wasVerified)
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
@@ -65,7 +65,7 @@ val NotificationsPerKeyKinds =
|
||||
ChannelMessageEvent.KIND,
|
||||
EphemeralChatEvent.KIND,
|
||||
BadgeAwardEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ZapPollEvent.KIND,
|
||||
PollEvent.KIND,
|
||||
PollResponseEvent.KIND,
|
||||
PublicMessageEvent.KIND,
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.watchers
|
||||
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip09Deletions.DeletionEvent
|
||||
@@ -45,7 +45,7 @@ val RepliesAndReactionsToAddressesKinds1 =
|
||||
GenericRepostEvent.KIND,
|
||||
ReportEvent.KIND,
|
||||
LnZapEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ZapPollEvent.KIND,
|
||||
CommentEvent.KIND,
|
||||
)
|
||||
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ package com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.watchers
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.edits.TextNoteModificationEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip03Timestamp.OtsEvent
|
||||
@@ -63,7 +63,7 @@ val RepliesAndReactionsKinds2 =
|
||||
TorrentCommentEvent.KIND,
|
||||
GitReplyEvent.KIND,
|
||||
PollResponseEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ZapPollEvent.KIND,
|
||||
)
|
||||
|
||||
fun filterRepliesAndReactionsToNotes(
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStory
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.nns.NNSEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
@@ -64,7 +64,7 @@ val SearchPostsByTextKinds1 =
|
||||
AudioHeaderEvent.KIND,
|
||||
AudioTrackEvent.KIND,
|
||||
PinListEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ZapPollEvent.KIND,
|
||||
ChannelCreateEvent.KIND,
|
||||
)
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geoHashOrScope
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
@@ -1039,7 +1039,7 @@ private fun RenderNoteRow(
|
||||
)
|
||||
}
|
||||
|
||||
is PollNoteEvent -> {
|
||||
is ZapPollEvent -> {
|
||||
RenderZapPoll(
|
||||
baseNote,
|
||||
makeItShort,
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note
|
||||
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
@@ -32,7 +34,6 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -58,8 +59,11 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
@@ -93,13 +97,15 @@ import com.vitorpamplona.amethyst.ui.theme.BigPadding
|
||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||
import com.vitorpamplona.amethyst.ui.theme.ButtonBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Font14SP
|
||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size14Modifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.SmallishBorder
|
||||
import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn
|
||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.allGoodColor
|
||||
import com.vitorpamplona.amethyst.ui.theme.grayText
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.ripple24dp
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip31Alts.AltTag
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -115,7 +121,7 @@ import kotlin.uuid.Uuid
|
||||
@Composable
|
||||
fun ZapZapPollNotePreview() {
|
||||
val event =
|
||||
PollNoteEvent(
|
||||
ZapPollEvent(
|
||||
id = "6ff9bc13d27490f6e3953325260bd996901a143de89886a0608c39e7d0160a72",
|
||||
pubKey = "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||
createdAt = 1729186078,
|
||||
@@ -188,7 +194,7 @@ fun ZapZapPollNotePreview() {
|
||||
@Composable
|
||||
fun ZapZapPollNotePreview2() {
|
||||
val event =
|
||||
PollNoteEvent(
|
||||
ZapPollEvent(
|
||||
id = "3064bf97800a4b04b612fc0fd498936eae75fffbdca5bbd09d19a6dc598530ab",
|
||||
pubKey = "f8ff11c7a7d3478355d3b4d174e5a473797a906ea4aa61aa9b6bc0652c1ea17a",
|
||||
createdAt = 1729191389,
|
||||
@@ -311,13 +317,6 @@ private fun OptionNote(
|
||||
modifier = Modifier.padding(vertical = 3.dp),
|
||||
) {
|
||||
if (!pollViewModel.canZap.value) {
|
||||
val color =
|
||||
if (poolOption.consensusThreadhold.value) {
|
||||
Color.Green.copy(alpha = 0.32f)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.mediumImportanceLink
|
||||
}
|
||||
|
||||
ZapVote(
|
||||
baseNote,
|
||||
poolOption,
|
||||
@@ -326,7 +325,7 @@ private fun OptionNote(
|
||||
RenderOptionAfterVote(
|
||||
baseNote,
|
||||
poolOption,
|
||||
color,
|
||||
poolOption.consensusThreadhold.value,
|
||||
canPreview,
|
||||
tags,
|
||||
backgroundColor,
|
||||
@@ -366,7 +365,7 @@ private fun OptionNote(
|
||||
private fun RenderOptionAfterVote(
|
||||
baseNote: Note,
|
||||
poolOption: PollOption,
|
||||
color: Color,
|
||||
isWinning: Boolean,
|
||||
canPreview: Boolean,
|
||||
tags: ImmutableListOfLists<String>,
|
||||
backgroundColor: MutableState<Color>,
|
||||
@@ -376,14 +375,25 @@ private fun RenderOptionAfterVote(
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth(0.75f)
|
||||
.clip(shape = QuoteBorder)
|
||||
.clip(SmallishBorder)
|
||||
.border(
|
||||
2.dp,
|
||||
color,
|
||||
QuoteBorder,
|
||||
width = 1.dp,
|
||||
color =
|
||||
if (isWinning) {
|
||||
MaterialTheme.colorScheme.allGoodColor
|
||||
} else {
|
||||
MaterialTheme.colorScheme.grayText
|
||||
},
|
||||
shape = SmallishBorder,
|
||||
).background(
|
||||
if (isWinning) {
|
||||
MaterialTheme.colorScheme.allGoodColor.copy(0.2f)
|
||||
} else {
|
||||
MaterialTheme.colorScheme.subtleBorder
|
||||
},
|
||||
),
|
||||
) {
|
||||
DisplayProgress(poolOption, color, modifier = Modifier.matchParentSize())
|
||||
DisplayProgress(poolOption, isWinning, modifier = Modifier.matchParentSize())
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -428,22 +438,29 @@ private fun RenderOptionAfterVote(
|
||||
@Composable
|
||||
private fun DisplayProgress(
|
||||
poolOption: PollOption,
|
||||
color: Color,
|
||||
isWinning: Boolean,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val progress by poolOption.tally
|
||||
// Animate the progress bar when a vote is cast
|
||||
val animatedProgress by animateFloatAsState(
|
||||
targetValue = poolOption.tally.value,
|
||||
animationSpec = tween(durationMillis = 800),
|
||||
)
|
||||
|
||||
// The LinearProgressIndicator has some weird update issues and renders inaccurate percentages.
|
||||
Box(modifier = modifier) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxWidth(progress)
|
||||
.fillMaxHeight()
|
||||
.background(color = color),
|
||||
) {
|
||||
}
|
||||
}
|
||||
val progressBarColor = if (isWinning) MaterialTheme.colorScheme.allGoodColor else MaterialTheme.colorScheme.primary
|
||||
|
||||
Box(
|
||||
modifier =
|
||||
modifier
|
||||
.alpha(0.32f)
|
||||
.drawWithContent {
|
||||
// Clip the drawing area to show only the progress amount
|
||||
clipRect(right = size.width * animatedProgress) {
|
||||
drawRect(progressBarColor)
|
||||
}
|
||||
drawContent()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -475,11 +492,11 @@ private fun RenderOptionBeforeVote(
|
||||
Box(
|
||||
Modifier
|
||||
.fillMaxWidth(0.75f)
|
||||
.clip(shape = QuoteBorder)
|
||||
.clip(SmallishBorder)
|
||||
.border(
|
||||
2.dp,
|
||||
MaterialTheme.colorScheme.primary,
|
||||
QuoteBorder,
|
||||
width = 1.dp,
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
shape = SmallishBorder,
|
||||
),
|
||||
) {
|
||||
Column(BigPadding) {
|
||||
@@ -568,7 +585,19 @@ fun ZapVote(
|
||||
showErrorMessageDialog = StringToastMsg(title, message)
|
||||
},
|
||||
onProgress = { scope.launch(Dispatchers.Main) { zappingProgress = it } },
|
||||
onPayViaIntent = {},
|
||||
onPayViaIntent = {
|
||||
if (it.size == 1) {
|
||||
val payable = it.first()
|
||||
payViaIntent(payable.invoice, context, { }) { error ->
|
||||
zappingProgress = 0f
|
||||
showErrorMessageDialog = StringToastMsg(stringRes(context, R.string.error_dialog_zap_error), error)
|
||||
}
|
||||
} else {
|
||||
val uid = Uuid.random().toString()
|
||||
accountViewModel.tempManualPaymentCache.put(uid, it)
|
||||
nav.nav(Route.ManualZapSplitPayment(uid))
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
wantsToZap = true
|
||||
|
||||
@@ -29,7 +29,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.vitorpamplona.amethyst.model.Account
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapEvent
|
||||
import com.vitorpamplona.quartz.nip57Zaps.LnZapRequestEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
@@ -53,7 +53,7 @@ class PollNoteViewModel : ViewModel() {
|
||||
private lateinit var account: Account
|
||||
private var pollNote: Note? = null
|
||||
|
||||
private var pollEvent: PollNoteEvent? = null
|
||||
private var pollEvent: ZapPollEvent? = null
|
||||
private var pollOptions: Map<Int, String>? = null
|
||||
private var valueMaximum: Long? = null
|
||||
private var valueMinimum: Long? = null
|
||||
@@ -76,7 +76,7 @@ class PollNoteViewModel : ViewModel() {
|
||||
fun load(note: Note?) {
|
||||
if (pollNote != note) {
|
||||
pollNote = note
|
||||
pollEvent = pollNote?.event as PollNoteEvent
|
||||
pollEvent = pollNote?.event as ZapPollEvent
|
||||
pollOptions = pollEvent?.pollOptions()
|
||||
valueMaximum = pollEvent?.maxAmount()
|
||||
valueMinimum = pollEvent?.minAmount()
|
||||
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.creators.zappolls
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import kotlinx.coroutines.CancellationException
|
||||
|
||||
@Composable
|
||||
fun ZapPollConsensusThreshold(pollViewModel: ShortNotePostViewModel) {
|
||||
var text by rememberSaveable { mutableStateOf("") }
|
||||
|
||||
pollViewModel.isValidConsensusThreshold.value = true
|
||||
if (text.isNotEmpty()) {
|
||||
try {
|
||||
val int = text.toInt()
|
||||
if (int !in 0..100) {
|
||||
pollViewModel.isValidConsensusThreshold.value = false
|
||||
} else {
|
||||
pollViewModel.zapPollConsensusThreshold = int
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) throw e
|
||||
pollViewModel.isValidConsensusThreshold.value = false
|
||||
}
|
||||
}
|
||||
|
||||
val colorInValid =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = MaterialTheme.colorScheme.error,
|
||||
unfocusedBorderColor = Color.Red,
|
||||
)
|
||||
val colorValid =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = MaterialTheme.colorScheme.primary,
|
||||
unfocusedBorderColor = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier.width(150.dp),
|
||||
colors = if (pollViewModel.isValidConsensusThreshold.value) colorValid else colorInValid,
|
||||
label = {
|
||||
Text(
|
||||
text = stringRes(R.string.poll_consensus_threshold),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.poll_consensus_threshold_percent),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ViewModelConstructorInComposable")
|
||||
@Preview
|
||||
@Composable
|
||||
fun ZapPollConsensusThresholdPreview() {
|
||||
ZapPollConsensusThreshold(ShortNotePostViewModel())
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.creators.zappolls
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.DateRange
|
||||
import androidx.compose.material3.DatePicker
|
||||
import androidx.compose.material3.DatePickerDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.SelectableDates
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TimePicker
|
||||
import androidx.compose.material3.TimePickerDialog
|
||||
import androidx.compose.material3.rememberDatePickerState
|
||||
import androidx.compose.material3.rememberTimePickerState
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.note.timeAheadNoDot
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.ZoneOffset
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ZapPollDeadlinePicker(model: ShortNotePostViewModel) {
|
||||
var showDatePicker by remember { mutableStateOf(false) }
|
||||
var showTimePicker by remember { mutableStateOf(false) }
|
||||
|
||||
// Get current time details
|
||||
val currentTime = Instant.ofEpochMilli(model.zapPollClosedAt * 1000).atZone(ZoneId.systemDefault()).toLocalDateTime()
|
||||
|
||||
val datePickerState =
|
||||
rememberDatePickerState(
|
||||
initialSelectedDateMillis = model.zapPollClosedAt * 1000,
|
||||
yearRange = currentTime.year..2050,
|
||||
selectableDates =
|
||||
object : SelectableDates {
|
||||
override fun isSelectableDate(utcTimeMillis: Long): Boolean {
|
||||
// Only allow today and future dates
|
||||
return utcTimeMillis >= System.currentTimeMillis() - 86400000 // minus 24h buffer
|
||||
}
|
||||
},
|
||||
)
|
||||
val timePickerState =
|
||||
rememberTimePickerState(
|
||||
initialHour = currentTime.hour,
|
||||
initialMinute = currentTime.minute,
|
||||
is24Hour = false, // Set to true if you prefer military time
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
OutlinedCard(
|
||||
onClick = { showDatePicker = true },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Icon(Icons.Default.DateRange, contentDescription = stringResource(R.string.accessibility_select_date))
|
||||
Spacer(Modifier.width(12.dp))
|
||||
|
||||
if (model.zapPollClosedAt < TimeUtils.oneMinuteFromNow()) {
|
||||
Text(stringRes(R.string.poll_closing_date_time) + " " + model.zapPollClosedAt, style = MaterialTheme.typography.bodyLarge)
|
||||
} else {
|
||||
Text(
|
||||
text = stringRes(R.string.poll_closing_in, timeAheadNoDot(model.zapPollClosedAt, context)),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Date Picker Dialog ---
|
||||
if (showDatePicker) {
|
||||
DatePickerDialog(
|
||||
onDismissRequest = { showDatePicker = false },
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
showDatePicker = false
|
||||
showTimePicker = true
|
||||
}) { Text(stringResource(R.string.next)) }
|
||||
},
|
||||
) {
|
||||
DatePicker(state = datePickerState)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Time Picker Dialog ---
|
||||
if (showTimePicker) {
|
||||
TimePickerDialog(
|
||||
title = {
|
||||
Text(stringResource(R.string.closing_time))
|
||||
},
|
||||
onDismissRequest = { showTimePicker = false },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
val datetimeLocalTimeZone =
|
||||
datePickerState.selectedDateMillis?.let { localDayAtZeroHourMillis ->
|
||||
(localDayAtZeroHourMillis / 1000) +
|
||||
(timePickerState.hour * TimeUtils.ONE_HOUR) +
|
||||
(timePickerState.minute * TimeUtils.ONE_MINUTE)
|
||||
} ?: TimeUtils.oneDayAhead()
|
||||
|
||||
// Get the offset from UTC for the current instant in the local time zone
|
||||
val offset: ZoneOffset = ZoneId.systemDefault().rules.getOffset(Instant.now())
|
||||
|
||||
model.zapPollClosedAt = datetimeLocalTimeZone - offset.totalSeconds
|
||||
|
||||
showTimePicker = false
|
||||
},
|
||||
) { Text(stringResource(R.string.confirm)) }
|
||||
},
|
||||
) {
|
||||
TimePicker(state = timePickerState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ViewModelConstructorInComposable")
|
||||
@Preview
|
||||
@Composable
|
||||
fun ZapPollDeadlinePickerPreview() {
|
||||
ZapPollDeadlinePicker(
|
||||
ShortNotePostViewModel(),
|
||||
)
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.creators.zappolls
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
|
||||
@Composable
|
||||
fun ZapPollField(postViewModel: ShortNotePostViewModel) {
|
||||
val optionsList = postViewModel.zapPollOptions
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
optionsList.forEach { value ->
|
||||
ZapPollOption(postViewModel, value.key)
|
||||
}
|
||||
|
||||
Spacer(Modifier.height(8.dp))
|
||||
|
||||
ZapPollDeadlinePicker(postViewModel)
|
||||
|
||||
ZapPollVoteValueRange(postViewModel)
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
// postViewModel.pollOptions[postViewModel.pollOptions.size] = ""
|
||||
optionsList[optionsList.size] = ""
|
||||
},
|
||||
border =
|
||||
BorderStroke(
|
||||
1.dp,
|
||||
MaterialTheme.colorScheme.outlineVariant,
|
||||
),
|
||||
colors =
|
||||
ButtonDefaults.outlinedButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.surface,
|
||||
),
|
||||
) {
|
||||
Icon(Icons.Default.Add, contentDescription = stringRes(R.string.add_poll_option_button))
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.creators.zappolls
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun ZapPollOption(
|
||||
pollViewModel: ShortNotePostViewModel,
|
||||
optionIndex: Int,
|
||||
) {
|
||||
Row {
|
||||
val deleteIcon: @Composable (() -> Unit) = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
pollViewModel.removeZapPollOption(optionIndex)
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringRes(R.string.clear),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
OutlinedTextField(
|
||||
modifier = Modifier.weight(1F),
|
||||
value = pollViewModel.zapPollOptions[optionIndex] ?: "",
|
||||
onValueChange = {
|
||||
pollViewModel.updateZapPollOption(optionIndex, it)
|
||||
},
|
||||
label = {
|
||||
Text(
|
||||
text = stringRes(R.string.poll_option_index).format(optionIndex + 1),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.poll_option_description),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(
|
||||
capitalization = KeyboardCapitalization.Sentences,
|
||||
),
|
||||
// colors = if (pollViewModel.pollOptions[optionIndex]?.isNotEmpty() == true) colorValid else
|
||||
// colorInValid,
|
||||
trailingIcon = if (optionIndex > 1) deleteIcon else null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ViewModelConstructorInComposable")
|
||||
@Preview
|
||||
@Composable
|
||||
fun ZapPollOptionPreview() {
|
||||
ZapPollOption(ShortNotePostViewModel(), 0)
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.ui.note.creators.zappolls
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.ShortNotePostViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
|
||||
@Composable
|
||||
fun ZapPollVoteValueRange(pollViewModel: ShortNotePostViewModel) {
|
||||
val colorInValid =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = MaterialTheme.colorScheme.error,
|
||||
unfocusedBorderColor = Color.Red,
|
||||
)
|
||||
val colorValid =
|
||||
OutlinedTextFieldDefaults.colors(
|
||||
focusedBorderColor = MaterialTheme.colorScheme.primary,
|
||||
unfocusedBorderColor = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = pollViewModel.zapPollValueMinimum?.toString() ?: "",
|
||||
onValueChange = { pollViewModel.updateMinZapAmountForPoll(it) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = if (pollViewModel.isValidValueMinimum.value) colorValid else colorInValid,
|
||||
label = {
|
||||
Text(
|
||||
text = stringRes(R.string.poll_zap_value_min),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.sats),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
Spacer(modifier = DoubleHorzSpacer)
|
||||
|
||||
OutlinedTextField(
|
||||
value = pollViewModel.zapPollValueMaximum?.toString() ?: "",
|
||||
onValueChange = { pollViewModel.updateMaxZapAmountForPoll(it) },
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
|
||||
modifier = Modifier.weight(1f),
|
||||
colors = if (pollViewModel.isValidValueMaximum.value) colorValid else colorInValid,
|
||||
label = {
|
||||
Text(
|
||||
text = stringRes(R.string.poll_zap_value_max),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringRes(R.string.sats),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = stringRes(R.string.poll_zap_value_min_max_explainer),
|
||||
color = MaterialTheme.colorScheme.placeholderText,
|
||||
modifier = Modifier.padding(vertical = 10.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ViewModelConstructorInComposable")
|
||||
@Preview
|
||||
@Composable
|
||||
fun ZapPollVoteValueRangePreview() {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
ZapPollVoteValueRange(ShortNotePostViewModel())
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,7 @@ import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.hasHashtags
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.hasAnyTaggedUser
|
||||
import com.vitorpamplona.quartz.nip72ModCommunities.definition.CommunityDefinitionEvent
|
||||
@@ -61,7 +61,7 @@ fun RenderZapPoll(
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: INav,
|
||||
) {
|
||||
val noteEvent = note.event as? PollNoteEvent ?: return
|
||||
val noteEvent = note.event as? ZapPollEvent ?: return
|
||||
val eventContent = noteEvent.content
|
||||
|
||||
val showReply by
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
@@ -102,7 +102,7 @@ class FollowPackFeedConversationsFeedFilter(
|
||||
): Boolean =
|
||||
(
|
||||
event is TextNoteEvent ||
|
||||
event is PollNoteEvent ||
|
||||
event is ZapPollEvent ||
|
||||
event is ChannelMessageEvent ||
|
||||
event is CommentEvent ||
|
||||
event is VoiceReplyEvent ||
|
||||
|
||||
+2
-2
@@ -34,7 +34,7 @@ import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
@@ -130,7 +130,7 @@ class FollowPackFeedNewThreadFeedFilter(
|
||||
noteEvent is GenericRepostEvent ||
|
||||
(noteEvent is LongTextNoteEvent && noteEvent.content.isNotEmpty()) ||
|
||||
(noteEvent is WikiNoteEvent && noteEvent.content.isNotEmpty()) ||
|
||||
noteEvent is PollNoteEvent ||
|
||||
noteEvent is ZapPollEvent ||
|
||||
noteEvent is HighlightEvent ||
|
||||
noteEvent is InteractiveStoryPrologueEvent ||
|
||||
noteEvent is CommentEvent ||
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.isTaggedGeoHash
|
||||
@@ -77,7 +77,7 @@ class GeoHashFeedFilter(
|
||||
event is WikiNoteEvent ||
|
||||
event is ChannelMessageEvent ||
|
||||
event is PrivateDmEvent ||
|
||||
event is PollNoteEvent ||
|
||||
event is ZapPollEvent ||
|
||||
event is AudioHeaderEvent
|
||||
) &&
|
||||
event.isTaggedGeoHash(geohash)
|
||||
|
||||
+2
-2
@@ -24,7 +24,7 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.nip22Comments.CommentKinds
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
@@ -43,7 +43,7 @@ val PostsByGeohashKinds =
|
||||
ChannelMessageEvent.KIND,
|
||||
LongTextNoteEvent.KIND,
|
||||
PollEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ZapPollEvent.KIND,
|
||||
ClassifiedsEvent.KIND,
|
||||
HighlightEvent.KIND,
|
||||
AudioTrackEvent.KIND,
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.hashtags.isTaggedHash
|
||||
@@ -81,7 +81,7 @@ class HashtagFeedFilter(
|
||||
event is WikiNoteEvent ||
|
||||
event is ChannelMessageEvent ||
|
||||
event is PrivateDmEvent ||
|
||||
event is PollNoteEvent ||
|
||||
event is ZapPollEvent ||
|
||||
event is AudioHeaderEvent
|
||||
) &&
|
||||
event.isTaggedHash(hashTag)
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
@@ -61,7 +61,7 @@ val PostsByHashtagKinds2 =
|
||||
AudioTrackEvent.KIND,
|
||||
AudioHeaderEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ZapPollEvent.KIND,
|
||||
)
|
||||
|
||||
fun filterPostsByHashtags(
|
||||
|
||||
+54
-3
@@ -94,15 +94,18 @@ import com.vitorpamplona.amethyst.ui.note.creators.secretEmoji.AddSecretEmojiBut
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.secretEmoji.SecretEmojiRequest
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.uploads.ImageVideoDescription
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.ShowUserSuggestionList
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.zappolls.ZapPollField
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.zapraiser.AddZapraiserButton
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.zapraiser.ZapRaiserRequest
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapTo
|
||||
import com.vitorpamplona.amethyst.ui.note.creators.zapsplits.ForwardZapToButton
|
||||
import com.vitorpamplona.amethyst.ui.note.types.ReplyRenderType
|
||||
import com.vitorpamplona.amethyst.ui.painterRes
|
||||
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.Size35dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size5dp
|
||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||
@@ -299,6 +302,15 @@ private fun NewPostScreenBody(
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsZapPoll) {
|
||||
Row(
|
||||
verticalAlignment = CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp),
|
||||
) {
|
||||
ZapPollField(postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
DisplayPreviews(postViewModel.urlPreviews, accountViewModel, nav)
|
||||
|
||||
if (postViewModel.wantsToMarkAsSensitive) {
|
||||
@@ -531,11 +543,24 @@ private fun BottomRowActions(postViewModel: ShortNotePostViewModel) {
|
||||
)
|
||||
|
||||
if (postViewModel.canUsePoll) {
|
||||
// These should be hashtag recommendations the user selects in the future.
|
||||
// val hashtag = stringRes(R.string.poll_hashtag)
|
||||
// postViewModel.includePollHashtagInMessage(postViewModel.wantsPoll, hashtag)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -584,6 +609,32 @@ 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,
|
||||
|
||||
+209
@@ -71,6 +71,12 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.privateDM.send.IMetaA
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.quartz.experimental.nip95.data.FileStorageEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.closedAt
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.consensusThreshold
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.maxAmount
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.minAmount
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.tags.PollOptionTag
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate
|
||||
@@ -230,6 +236,20 @@ open class ShortNotePostViewModel :
|
||||
var pollOptions: SnapshotStateMap<Int, OptionTag> = newStateMapPollOptions()
|
||||
var closedAt by mutableLongStateOf(TimeUtils.oneDayAhead())
|
||||
|
||||
// ZapPolls
|
||||
var canUseZapPoll by mutableStateOf(false)
|
||||
var wantsZapPoll by mutableStateOf(false)
|
||||
var zapPollOptions: SnapshotStateMap<Int, String> = newStateMapZapPollOptions()
|
||||
var zapPollValueMaximum by mutableStateOf<Long?>(null)
|
||||
var zapPollValueMinimum by mutableStateOf<Long?>(null)
|
||||
var zapPollConsensusThreshold: Int? = null
|
||||
var zapPollClosedAt by mutableLongStateOf(TimeUtils.oneDayAhead())
|
||||
|
||||
var isValidValueMaximum = mutableStateOf(true)
|
||||
var isValidValueMinimum = mutableStateOf(true)
|
||||
var isValidConsensusThreshold = mutableStateOf(true)
|
||||
var isValidClosedAt = mutableStateOf(true)
|
||||
|
||||
// Invoices
|
||||
var canAddInvoice by mutableStateOf(false)
|
||||
var wantsInvoice by mutableStateOf(false)
|
||||
@@ -333,6 +353,7 @@ open class ShortNotePostViewModel :
|
||||
canAddInvoice = user.lnAddress() != null
|
||||
canAddZapRaiser = user.lnAddress() != null
|
||||
canUsePoll = originalNote == null
|
||||
canUseZapPoll = originalNote == null
|
||||
multiOrchestrator = null
|
||||
|
||||
quote?.let { quotedNote ->
|
||||
@@ -417,6 +438,10 @@ open class ShortNotePostViewModel :
|
||||
if (draftEvent is PollEvent) {
|
||||
loadFromDraft(draftEvent)
|
||||
}
|
||||
|
||||
if (draftEvent is ZapPollEvent) {
|
||||
loadFromDraft(draftEvent)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadFromDraft(draftEvent: TextNoteEvent) {
|
||||
@@ -489,12 +514,14 @@ open class ShortNotePostViewModel :
|
||||
}
|
||||
|
||||
canUsePoll = originalNote == null
|
||||
canUseZapPoll = originalNote == null
|
||||
|
||||
if (forwardZapTo.value.items.isNotEmpty()) {
|
||||
wantsForwardZapTo = true
|
||||
}
|
||||
|
||||
wantsPoll = false
|
||||
wantsZapPoll = false
|
||||
|
||||
message = TextFieldValue(draftEvent.content)
|
||||
|
||||
@@ -550,11 +577,14 @@ open class ShortNotePostViewModel :
|
||||
}
|
||||
|
||||
canUsePoll = originalNote == null
|
||||
canUseZapPoll = originalNote == null
|
||||
|
||||
if (forwardZapTo.value.items.isNotEmpty()) {
|
||||
wantsForwardZapTo = true
|
||||
}
|
||||
|
||||
wantsZapPoll = false
|
||||
|
||||
val polls = draftEvent.options()
|
||||
wantsPoll = polls.isNotEmpty()
|
||||
|
||||
@@ -571,6 +601,80 @@ open class ShortNotePostViewModel :
|
||||
urlPreviews.update(message)
|
||||
}
|
||||
|
||||
private fun loadFromDraft(draftEvent: ZapPollEvent) {
|
||||
canAddInvoice = accountViewModel.userProfile().lnAddress() != null
|
||||
canAddZapRaiser = accountViewModel.userProfile().lnAddress() != null
|
||||
multiOrchestrator = null
|
||||
|
||||
val localForwardZapTo = draftEvent.tags.filter { it.size > 1 && it[0] == "zap" }
|
||||
forwardZapTo.value = SplitBuilder()
|
||||
localForwardZapTo.forEach {
|
||||
val user = LocalCache.getOrCreateUser(it[1])
|
||||
val value = it.last().toFloatOrNull() ?: 0f
|
||||
forwardZapTo.value.addItem(user, value)
|
||||
}
|
||||
forwardZapToEditting.value = TextFieldValue("")
|
||||
wantsForwardZapTo = localForwardZapTo.isNotEmpty()
|
||||
|
||||
wantsToMarkAsSensitive = draftEvent.isSensitive()
|
||||
contentWarningDescription = draftEvent.contentWarningReason() ?: ""
|
||||
|
||||
val draftExpiration = draftEvent.tags.expiration()
|
||||
wantsExpirationDate = draftExpiration != null
|
||||
expirationDate = draftExpiration ?: TimeUtils.oneDayAhead()
|
||||
|
||||
val geohash = draftEvent.getGeoHash()
|
||||
wantsToAddGeoHash = geohash != null
|
||||
if (geohash != null) {
|
||||
wantsExclusiveGeoPost = draftEvent.kind == CommentEvent.KIND
|
||||
}
|
||||
|
||||
val zapRaiser = draftEvent.zapraiserAmount()
|
||||
wantsZapRaiser = zapRaiser != null
|
||||
zapRaiserAmount.value = null
|
||||
if (zapRaiser != null) {
|
||||
zapRaiserAmount.value = zapRaiser
|
||||
}
|
||||
|
||||
eTags =
|
||||
draftEvent.tags.filter { it.size > 1 && (it[0] == "e" || it[0] == "a") && it.getOrNull(3) != "fork" }.mapNotNull {
|
||||
val note = LocalCache.checkGetOrCreateNote(it[1])
|
||||
note
|
||||
}
|
||||
|
||||
pTags =
|
||||
draftEvent.tags.filter { it.size > 1 && it[0] == "p" }.map {
|
||||
LocalCache.getOrCreateUser(it[1])
|
||||
}
|
||||
|
||||
canUsePoll = originalNote == null
|
||||
canUseZapPoll = originalNote == null
|
||||
|
||||
if (forwardZapTo.value.items.isNotEmpty()) {
|
||||
wantsForwardZapTo = true
|
||||
}
|
||||
|
||||
wantsPoll = false
|
||||
|
||||
val polls = draftEvent.pollOptionsArray()
|
||||
wantsZapPoll = polls.isNotEmpty()
|
||||
|
||||
polls.forEach { tag ->
|
||||
zapPollOptions[tag.index] = tag.descriptor
|
||||
}
|
||||
|
||||
zapPollValueMinimum = draftEvent.minAmount()
|
||||
zapPollValueMaximum = draftEvent.maxAmount()
|
||||
zapPollConsensusThreshold = draftEvent.consensusThreshold()?.let { (it * 100).toInt() }
|
||||
zapPollClosedAt = draftEvent.closedAt() ?: TimeUtils.oneDayAhead()
|
||||
|
||||
message = TextFieldValue(draftEvent.content)
|
||||
|
||||
iMetaAttachments.addAll(draftEvent.imetas())
|
||||
|
||||
urlPreviews.update(message)
|
||||
}
|
||||
|
||||
suspend fun sendPostSync() {
|
||||
// Upload voice message first if it hasn't been uploaded yet
|
||||
if (voiceRecording != null && voiceMetadata == null) {
|
||||
@@ -731,6 +835,35 @@ open class ShortNotePostViewModel :
|
||||
contentWarningReason?.let { contentWarning(it) }
|
||||
localExpirationDate?.let { expiration(it) }
|
||||
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
} else if (wantsZapPoll) {
|
||||
val options = zapPollOptions.map { PollOptionTag(it.key, it.value) }
|
||||
|
||||
if (options.isEmpty()) return null
|
||||
|
||||
val quotes = findNostrUris(tagger.message)
|
||||
val relays =
|
||||
accountViewModel.account.nip65RelayList.outboxFlow.value
|
||||
.toList()
|
||||
|
||||
ZapPollEvent.build(tagger.message, options) {
|
||||
zapPollValueMinimum?.let { minAmount(it) }
|
||||
zapPollValueMaximum?.let { maxAmount(it) }
|
||||
zapPollClosedAt?.let { closedAt(it) }
|
||||
zapPollConsensusThreshold?.let { consensusThreshold(it / 100.0) }
|
||||
|
||||
pTags(tagger.directMentionsUsers.map { it.toPTag() })
|
||||
quotes(quotes)
|
||||
hashtags(findHashtags(tagger.message))
|
||||
|
||||
geoHash?.let { geohash(it) }
|
||||
localZapRaiserAmount?.let { zapraiser(it) }
|
||||
zapReceiver?.let { zapSplits(it) }
|
||||
contentWarningReason?.let { contentWarning(it) }
|
||||
localExpirationDate?.let { expiration(it) }
|
||||
|
||||
emojis(emojis)
|
||||
imetas(usedAttachments)
|
||||
}
|
||||
@@ -915,6 +1048,13 @@ open class ShortNotePostViewModel :
|
||||
pollOptions = newStateMapPollOptions()
|
||||
closedAt = TimeUtils.oneDayAhead()
|
||||
|
||||
wantsZapPoll = false
|
||||
zapPollOptions = newStateMapZapPollOptions()
|
||||
zapPollValueMaximum = null
|
||||
zapPollValueMinimum = null
|
||||
zapPollConsensusThreshold = null
|
||||
zapPollClosedAt = TimeUtils.oneDayAhead()
|
||||
|
||||
wantsInvoice = false
|
||||
wantsZapRaiser = false
|
||||
zapRaiserAmount.value = null
|
||||
@@ -1032,6 +1172,12 @@ open class ShortNotePostViewModel :
|
||||
1 to OptionTag(RandomInstance.randomChars(6), ""),
|
||||
)
|
||||
|
||||
private fun newStateMapZapPollOptions(): SnapshotStateMap<Int, String> =
|
||||
mutableStateMapOf(
|
||||
0 to "",
|
||||
1 to "",
|
||||
)
|
||||
|
||||
fun canPost(): Boolean {
|
||||
// Voice messages can be posted without text (with either uploaded or pending recording)
|
||||
if (voiceMetadata != null || voiceRecording != null) {
|
||||
@@ -1052,6 +1198,14 @@ open class ShortNotePostViewModel :
|
||||
closedAt > TimeUtils.oneMinuteFromNow()
|
||||
)
|
||||
) &&
|
||||
(
|
||||
!wantsZapPoll ||
|
||||
(
|
||||
zapPollOptions.values.all { it.isNotEmpty() } &&
|
||||
isValidValueMinimum.value &&
|
||||
isValidValueMaximum.value
|
||||
)
|
||||
) &&
|
||||
multiOrchestrator == null
|
||||
}
|
||||
|
||||
@@ -1254,6 +1408,61 @@ open class ShortNotePostViewModel :
|
||||
this.putAll(newEntries)
|
||||
}
|
||||
|
||||
// ---
|
||||
// Zap Polls
|
||||
// ---
|
||||
|
||||
fun updateMinZapAmountForPoll(textMin: String) {
|
||||
zapPollValueMinimum = textMin.toLongOrNull()?.takeIf { it > 0 }
|
||||
checkMinMax()
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
fun updateMaxZapAmountForPoll(textMax: String) {
|
||||
zapPollValueMaximum = textMax.toLongOrNull()?.takeIf { it > 0 }
|
||||
checkMinMax()
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
fun checkMinMax() {
|
||||
if ((zapPollValueMinimum ?: 0) > (zapPollValueMaximum ?: Long.MAX_VALUE)) {
|
||||
isValidValueMinimum.value = false
|
||||
isValidValueMaximum.value = false
|
||||
} else {
|
||||
isValidValueMinimum.value = true
|
||||
isValidValueMaximum.value = true
|
||||
}
|
||||
}
|
||||
|
||||
fun removeZapPollOption(optionIndex: Int) {
|
||||
zapPollOptions.removeOrderedZapPoll(optionIndex)
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
private fun MutableMap<Int, String>.removeOrderedZapPoll(index: Int) {
|
||||
val keyList = keys
|
||||
val elementList = values.toMutableList()
|
||||
run stop@{
|
||||
for (i in index until elementList.size) {
|
||||
val nextIndex = i + 1
|
||||
if (nextIndex == elementList.size) return@stop
|
||||
elementList[i] = elementList[nextIndex].also { elementList[nextIndex] = "null" }
|
||||
}
|
||||
}
|
||||
elementList.removeAt(elementList.size - 1)
|
||||
val newEntries = keyList.zip(elementList) { key, content -> Pair(key, content) }
|
||||
this.clear()
|
||||
this.putAll(newEntries)
|
||||
}
|
||||
|
||||
fun updateZapPollOption(
|
||||
optionIndex: Int,
|
||||
text: String,
|
||||
) {
|
||||
zapPollOptions[optionIndex] = text
|
||||
draftTag.newVersion()
|
||||
}
|
||||
|
||||
fun toggleMarkAsSensitive() {
|
||||
wantsToMarkAsSensitive = !wantsToMarkAsSensitive
|
||||
draftTag.newVersion()
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@ import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
@@ -81,7 +81,7 @@ class HomeConversationsFeedFilter(
|
||||
): Boolean =
|
||||
(
|
||||
event is TextNoteEvent ||
|
||||
event is PollNoteEvent ||
|
||||
event is ZapPollEvent ||
|
||||
event is PollResponseEvent ||
|
||||
event is ChannelMessageEvent ||
|
||||
event is CommentEvent ||
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ import com.vitorpamplona.amethyst.ui.dal.FilterByListParams
|
||||
import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.RepostEvent
|
||||
@@ -116,7 +116,7 @@ class HomeNewThreadFeedFilter(
|
||||
noteEvent is GenericRepostEvent ||
|
||||
(noteEvent is LongTextNoteEvent && noteEvent.content.isNotEmpty()) ||
|
||||
(noteEvent is WikiNoteEvent && noteEvent.content.isNotEmpty()) ||
|
||||
noteEvent is PollNoteEvent ||
|
||||
noteEvent is ZapPollEvent ||
|
||||
noteEvent is PollEvent ||
|
||||
noteEvent is HighlightEvent ||
|
||||
noteEvent is InteractiveStoryPrologueEvent ||
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.ephemChat.chat.EphemeralChatEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
@@ -60,7 +60,7 @@ val HomePostsNewThreadKinds =
|
||||
HighlightEvent.KIND,
|
||||
WikiNoteEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ZapPollEvent.KIND,
|
||||
PollEvent.KIND,
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
ChessGameEvent.KIND,
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.forks.IForkableEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||
@@ -106,7 +106,7 @@ class NotificationFeedFilter(
|
||||
LiveActivitiesChatMessageEvent.KIND,
|
||||
PictureEvent.KIND,
|
||||
PollEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ZapPollEvent.KIND,
|
||||
PrivateDmEvent.KIND,
|
||||
PublicMessageEvent.KIND,
|
||||
VideoNormalEvent.KIND,
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip22Comments.CommentEvent
|
||||
import com.vitorpamplona.quartz.nip28PublicChat.message.ChannelMessageEvent
|
||||
@@ -64,7 +64,7 @@ class UserProfileConversationsFeedFilter(
|
||||
it.author == user &&
|
||||
(
|
||||
it.event is TextNoteEvent ||
|
||||
it.event is PollNoteEvent ||
|
||||
it.event is ZapPollEvent ||
|
||||
it.event is PollResponseEvent ||
|
||||
it.event is ChannelMessageEvent ||
|
||||
it.event is LiveActivitiesChatMessageEvent ||
|
||||
|
||||
+2
-2
@@ -26,7 +26,7 @@ import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
@@ -64,7 +64,7 @@ val UserProfilePostKinds2 =
|
||||
InteractiveStoryPrologueEvent.KIND,
|
||||
CommentEvent.KIND,
|
||||
VoiceReplyEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ZapPollEvent.KIND,
|
||||
PinListEvent.KIND,
|
||||
)
|
||||
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.isTaggedUser
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
@@ -78,7 +78,7 @@ class UserProfileMutualFeedFilter(
|
||||
it.event is LongTextNoteEvent ||
|
||||
it.event is WikiNoteEvent ||
|
||||
it.event is NipTextEvent ||
|
||||
it.event is PollNoteEvent ||
|
||||
it.event is ZapPollEvent ||
|
||||
it.event is HighlightEvent ||
|
||||
it.event is InteractiveStoryPrologueEvent ||
|
||||
it.event is AudioTrackEvent ||
|
||||
|
||||
+2
-2
@@ -31,7 +31,7 @@ import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStoryPrologueEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.AddressableEvent
|
||||
import com.vitorpamplona.quartz.nip10Notes.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.nip18Reposts.GenericRepostEvent
|
||||
@@ -80,7 +80,7 @@ class UserProfileNewThreadFeedFilter(
|
||||
it.event is LongTextNoteEvent ||
|
||||
it.event is WikiNoteEvent ||
|
||||
it.event is NipTextEvent ||
|
||||
it.event is PollNoteEvent ||
|
||||
it.event is ZapPollEvent ||
|
||||
it.event is PollEvent ||
|
||||
it.event is HighlightEvent ||
|
||||
it.event is InteractiveStoryPrologueEvent ||
|
||||
|
||||
+2
-2
@@ -25,7 +25,7 @@ import com.vitorpamplona.quartz.experimental.audio.header.AudioHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.audio.track.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStorySceneEvent
|
||||
import com.vitorpamplona.quartz.experimental.nipsOnNostr.NipTextEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
|
||||
@@ -58,7 +58,7 @@ val PostsByRelayKinds2 =
|
||||
AudioTrackEvent.KIND,
|
||||
AudioHeaderEvent.KIND,
|
||||
NipTextEvent.KIND,
|
||||
PollNoteEvent.KIND,
|
||||
ZapPollEvent.KIND,
|
||||
)
|
||||
|
||||
fun filterPostsByRelay(
|
||||
|
||||
+2
-2
@@ -146,7 +146,7 @@ import com.vitorpamplona.quartz.experimental.profileGallery.ProfileGalleryEntryE
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.relationshipStatus.ContactCardEvent
|
||||
import com.vitorpamplona.quartz.experimental.trustedAssertions.list.TrustProviderListEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.relay.client.stats.ErrorDebugMessage
|
||||
@@ -619,7 +619,7 @@ private fun kindDisplayName(kind: Int): Int =
|
||||
PeopleListEvent.KIND -> R.string.kind_people_lists
|
||||
PictureEvent.KIND -> R.string.kind_pictures
|
||||
PinListEvent.KIND -> R.string.kind_pins
|
||||
PollNoteEvent.KIND -> R.string.kind_zap_poll
|
||||
ZapPollEvent.KIND -> R.string.kind_zap_poll
|
||||
PollEvent.KIND -> R.string.kind_poll
|
||||
PollResponseEvent.KIND -> R.string.kind_poll_response
|
||||
PrivateDmEvent.KIND -> R.string.kind_nip04_dms
|
||||
|
||||
+2
-2
@@ -199,7 +199,7 @@ import com.vitorpamplona.quartz.experimental.interactiveStories.InteractiveStory
|
||||
import com.vitorpamplona.quartz.experimental.medical.FhirResourceEvent
|
||||
import com.vitorpamplona.quartz.experimental.nip95.header.FileStorageHeaderEvent
|
||||
import com.vitorpamplona.quartz.experimental.publicMessages.PublicMessageEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.experimental.zapPolls.ZapPollEvent
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.geohash.geoHashOrScope
|
||||
import com.vitorpamplona.quartz.nip04Dm.messages.PrivateDmEvent
|
||||
@@ -692,7 +692,7 @@ private fun FullBleedNoteCompose(
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
} else if (noteEvent is PollNoteEvent) {
|
||||
} else if (noteEvent is ZapPollEvent) {
|
||||
RenderZapPoll(
|
||||
baseNote,
|
||||
false,
|
||||
|
||||
Reference in New Issue
Block a user