mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2024-09-30 00:40:49 +00:00
1. Adds Classified creation from Amethyst.
2. Adds relay information for Replaceable events.
This commit is contained in:
parent
8f1fbe10e9
commit
f458f00edc
@ -3,30 +3,39 @@
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
@ -78,7 +78,7 @@ height="80">](https://github.com/vitorpamplona/amethyst/releases)
|
||||
- [x] Moderated Communities (NIP-72)
|
||||
- [x] Emoji Packs (Kind:30030)
|
||||
- [x] Personal Emoji Lists (Kind:10030)
|
||||
- [x] Classifieds (Kind:30403)
|
||||
- [x] Classifieds (Kind:30402)
|
||||
- [x] Private Messages and Small Groups (NIP-24)
|
||||
- [x] Gift Wraps & Seals (NIP-59)
|
||||
- [x] Versioned Encrypted Payloads (NIP-44)
|
||||
|
@ -4,6 +4,8 @@ import android.content.res.Resources
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.os.ConfigurationCompat
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.asFlow
|
||||
@ -30,6 +32,7 @@ import com.vitorpamplona.quartz.events.ChannelCreateEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ChannelMetadataEvent
|
||||
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.events.Contact
|
||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||
import com.vitorpamplona.quartz.events.DeletionEvent
|
||||
@ -55,6 +58,7 @@ import com.vitorpamplona.quartz.events.MuteListEvent
|
||||
import com.vitorpamplona.quartz.events.NIP24Factory
|
||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||
import com.vitorpamplona.quartz.events.PollNoteEvent
|
||||
import com.vitorpamplona.quartz.events.Price
|
||||
import com.vitorpamplona.quartz.events.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.events.ReactionEvent
|
||||
import com.vitorpamplona.quartz.events.RelayAuthEvent
|
||||
@ -91,6 +95,7 @@ import kotlinx.coroutines.withTimeoutOrNull
|
||||
import java.math.BigDecimal
|
||||
import java.net.Proxy
|
||||
import java.util.Locale
|
||||
import java.util.UUID
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
val DefaultChannels = setOf(
|
||||
@ -447,7 +452,7 @@ class Account(
|
||||
val contactList = userProfile().latestContactList
|
||||
|
||||
if (contactList != null && contactList.tags.isNotEmpty()) {
|
||||
val event = ContactListEvent.updateRelayList(
|
||||
ContactListEvent.updateRelayList(
|
||||
earlierVersion = contactList,
|
||||
relayUse = relays,
|
||||
signer = signer
|
||||
@ -456,7 +461,7 @@ class Account(
|
||||
LocalCache.justConsume(it, null)
|
||||
}
|
||||
} else {
|
||||
val event = ContactListEvent.createFromScratch(
|
||||
ContactListEvent.createFromScratch(
|
||||
followUsers = listOf(),
|
||||
followTags = listOf(),
|
||||
followGeohashes = listOf(),
|
||||
@ -1025,6 +1030,64 @@ class Account(
|
||||
}
|
||||
}
|
||||
|
||||
fun sendClassifieds(
|
||||
title: String,
|
||||
price: Price,
|
||||
condition: ClassifiedsEvent.CONDITION,
|
||||
location: String,
|
||||
category: String,
|
||||
message: String,
|
||||
replyTo: List<Note>?,
|
||||
mentions: List<User>?,
|
||||
directMentions: Set<HexKey>,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
wantsToMarkAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long? = null,
|
||||
relayList: List<Relay>? = null,
|
||||
geohash: String? = null
|
||||
) {
|
||||
if (!isWriteable()) return
|
||||
|
||||
val repliesToHex = replyTo?.filter { it.address() == null }?.map { it.idHex }
|
||||
val mentionsHex = mentions?.map { it.pubkeyHex }
|
||||
val addresses = replyTo?.mapNotNull { it.address() }
|
||||
|
||||
ClassifiedsEvent.create(
|
||||
dTag = UUID.randomUUID().toString(),
|
||||
title = title,
|
||||
price = price,
|
||||
condition = condition,
|
||||
summary = message,
|
||||
image = null,
|
||||
location = location,
|
||||
category = category,
|
||||
message = message,
|
||||
replyTos = repliesToHex,
|
||||
mentions = mentionsHex,
|
||||
addresses = addresses,
|
||||
zapReceiver = zapReceiver,
|
||||
markAsSensitive = wantsToMarkAsSensitive,
|
||||
zapRaiserAmount = zapRaiserAmount,
|
||||
directMentions = directMentions,
|
||||
geohash = geohash,
|
||||
signer = signer
|
||||
) {
|
||||
Client.send(it, relayList = relayList)
|
||||
LocalCache.justConsume(it, null)
|
||||
|
||||
replyTo?.forEach {
|
||||
it.event?.let {
|
||||
Client.send(it, relayList = relayList)
|
||||
}
|
||||
}
|
||||
addresses?.forEach {
|
||||
LocalCache.getAddressableNoteIfExists(it.toTag())?.event?.let {
|
||||
Client.send(it, relayList = relayList)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun sendPost(
|
||||
message: String,
|
||||
replyTo: List<Note>?,
|
||||
@ -1500,7 +1563,7 @@ class Account(
|
||||
signer = signer
|
||||
) {
|
||||
Client.send(it)
|
||||
LocalCache.consume(it)
|
||||
LocalCache.consume(it, null)
|
||||
}
|
||||
} else {
|
||||
MuteListEvent.createListWithWord(
|
||||
@ -1509,7 +1572,7 @@ class Account(
|
||||
signer = signer
|
||||
) {
|
||||
Client.send(it)
|
||||
LocalCache.consume(it)
|
||||
LocalCache.consume(it, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1525,7 +1588,7 @@ class Account(
|
||||
signer = signer
|
||||
) {
|
||||
Client.send(it)
|
||||
LocalCache.consume(it)
|
||||
LocalCache.consume(it, null)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1539,7 +1602,7 @@ class Account(
|
||||
signer = signer
|
||||
) {
|
||||
Client.send(it)
|
||||
LocalCache.consume(it)
|
||||
LocalCache.consume(it, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1555,7 +1618,7 @@ class Account(
|
||||
signer = signer
|
||||
) {
|
||||
Client.send(it)
|
||||
LocalCache.consume(it)
|
||||
LocalCache.consume(it, null)
|
||||
}
|
||||
} else {
|
||||
MuteListEvent.createListWithUser(
|
||||
@ -1564,7 +1627,7 @@ class Account(
|
||||
signer = signer
|
||||
) {
|
||||
Client.send(it)
|
||||
LocalCache.consume(it)
|
||||
LocalCache.consume(it, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1580,7 +1643,7 @@ class Account(
|
||||
signer = signer
|
||||
) {
|
||||
Client.send(it)
|
||||
LocalCache.consume(it)
|
||||
LocalCache.consume(it, null)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1594,7 +1657,7 @@ class Account(
|
||||
signer = signer
|
||||
) {
|
||||
Client.send(it)
|
||||
LocalCache.consume(it)
|
||||
LocalCache.consume(it, null)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -276,7 +276,7 @@ object LocalCache {
|
||||
// avoids processing empty contact lists.
|
||||
if (event.createdAt > (user.latestContactList?.createdAt ?: 0) && !event.tags.isEmpty()) {
|
||||
user.updateContactList(event)
|
||||
// Log.d("CL", "AAA ${user.toBestDisplayName()} ${follows.size}")
|
||||
// Log.d("CL", "Consumed contact list ${user.toNostrUri()} ${event.relays()?.size}")
|
||||
}
|
||||
}
|
||||
|
||||
@ -426,16 +426,16 @@ object LocalCache {
|
||||
}
|
||||
}
|
||||
|
||||
fun consume(event: MuteListEvent) { consumeBaseReplaceable(event) }
|
||||
fun consume(event: PeopleListEvent) { consumeBaseReplaceable(event) }
|
||||
private fun consume(event: AdvertisedRelayListEvent) { consumeBaseReplaceable(event) }
|
||||
private fun consume(event: CommunityDefinitionEvent, relay: Relay?) { consumeBaseReplaceable(event) }
|
||||
fun consume(event: EmojiPackSelectionEvent) { consumeBaseReplaceable(event) }
|
||||
private fun consume(event: EmojiPackEvent) { consumeBaseReplaceable(event) }
|
||||
private fun consume(event: ClassifiedsEvent) { consumeBaseReplaceable(event) }
|
||||
private fun consume(event: PinListEvent) { consumeBaseReplaceable(event) }
|
||||
private fun consume(event: RelaySetEvent) { consumeBaseReplaceable(event) }
|
||||
private fun consume(event: AudioTrackEvent) { consumeBaseReplaceable(event) }
|
||||
fun consume(event: MuteListEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
fun consume(event: PeopleListEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
private fun consume(event: AdvertisedRelayListEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
private fun consume(event: CommunityDefinitionEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
fun consume(event: EmojiPackSelectionEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
private fun consume(event: EmojiPackEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
private fun consume(event: ClassifiedsEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
private fun consume(event: PinListEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
private fun consume(event: RelaySetEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
private fun consume(event: AudioTrackEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
fun consume(event: StatusEvent, relay: Relay?) {
|
||||
val version = getOrCreateNote(event.id)
|
||||
val note = getOrCreateAddressableNote(event.address())
|
||||
@ -458,7 +458,7 @@ object LocalCache {
|
||||
}
|
||||
}
|
||||
|
||||
fun consume(event: BadgeDefinitionEvent) { consumeBaseReplaceable(event) }
|
||||
fun consume(event: BadgeDefinitionEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
|
||||
fun consume(event: BadgeProfilesEvent) {
|
||||
val version = getOrCreateNote(event.id)
|
||||
@ -504,14 +504,14 @@ object LocalCache {
|
||||
refreshObservers(note)
|
||||
}
|
||||
|
||||
private fun comsume(event: NNSEvent) { consumeBaseReplaceable(event) }
|
||||
fun consume(event: AppDefinitionEvent) { consumeBaseReplaceable(event) }
|
||||
private fun consume(event: CalendarEvent) { consumeBaseReplaceable(event) }
|
||||
private fun consume(event: CalendarDateSlotEvent) { consumeBaseReplaceable(event) }
|
||||
private fun consume(event: CalendarTimeSlotEvent) { consumeBaseReplaceable(event) }
|
||||
private fun consume(event: CalendarRSVPEvent) { consumeBaseReplaceable(event) }
|
||||
private fun comsume(event: NNSEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
fun consume(event: AppDefinitionEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
private fun consume(event: CalendarEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
private fun consume(event: CalendarDateSlotEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
private fun consume(event: CalendarTimeSlotEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
private fun consume(event: CalendarRSVPEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
|
||||
private fun consumeBaseReplaceable(event: BaseAddressableEvent) {
|
||||
private fun consumeBaseReplaceable(event: BaseAddressableEvent, relay: Relay?) {
|
||||
val version = getOrCreateNote(event.id)
|
||||
val note = getOrCreateAddressableNote(event.address())
|
||||
val author = getOrCreateUser(event.pubKey)
|
||||
@ -521,6 +521,11 @@ object LocalCache {
|
||||
version.moveAllReferencesTo(note)
|
||||
}
|
||||
|
||||
if (relay != null) {
|
||||
author.addRelayBeingUsed(relay, event.createdAt)
|
||||
note.addRelay(relay)
|
||||
}
|
||||
|
||||
// Already processed this event.
|
||||
if (note.event?.id() == event.id()) return
|
||||
|
||||
@ -531,7 +536,7 @@ object LocalCache {
|
||||
}
|
||||
}
|
||||
|
||||
fun consume(event: AppRecommendationEvent) { consumeBaseReplaceable(event) }
|
||||
fun consume(event: AppRecommendationEvent, relay: Relay?) { consumeBaseReplaceable(event, relay) }
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun consume(event: RecommendRelayEvent) {
|
||||
@ -1511,26 +1516,26 @@ object LocalCache {
|
||||
|
||||
try {
|
||||
when (event) {
|
||||
is AdvertisedRelayListEvent -> consume(event)
|
||||
is AppDefinitionEvent -> consume(event)
|
||||
is AppRecommendationEvent -> consume(event)
|
||||
is AdvertisedRelayListEvent -> consume(event, relay)
|
||||
is AppDefinitionEvent -> consume(event, relay)
|
||||
is AppRecommendationEvent -> consume(event, relay)
|
||||
is AudioHeaderEvent -> consume(event, relay)
|
||||
is AudioTrackEvent -> consume(event)
|
||||
is AudioTrackEvent -> consume(event, relay)
|
||||
is BadgeAwardEvent -> consume(event)
|
||||
is BadgeDefinitionEvent -> consume(event)
|
||||
is BadgeDefinitionEvent -> consume(event, relay)
|
||||
is BadgeProfilesEvent -> consume(event)
|
||||
is BookmarkListEvent -> consume(event)
|
||||
is CalendarEvent -> consume(event)
|
||||
is CalendarDateSlotEvent -> consume(event)
|
||||
is CalendarTimeSlotEvent -> consume(event)
|
||||
is CalendarRSVPEvent -> consume(event)
|
||||
is CalendarEvent -> consume(event, relay)
|
||||
is CalendarDateSlotEvent -> consume(event, relay)
|
||||
is CalendarTimeSlotEvent -> consume(event, relay)
|
||||
is CalendarRSVPEvent -> consume(event, relay)
|
||||
is ChannelCreateEvent -> consume(event)
|
||||
is ChannelHideMessageEvent -> consume(event)
|
||||
is ChannelMessageEvent -> consume(event, relay)
|
||||
is ChannelMetadataEvent -> consume(event)
|
||||
is ChannelMuteUserEvent -> consume(event)
|
||||
is ChatMessageEvent -> consume(event, relay)
|
||||
is ClassifiedsEvent -> consume(event)
|
||||
is ClassifiedsEvent -> consume(event, relay)
|
||||
is CommunityDefinitionEvent -> consume(event, relay)
|
||||
is CommunityPostApprovalEvent -> {
|
||||
event.containedPost()?.let {
|
||||
@ -1540,8 +1545,8 @@ object LocalCache {
|
||||
}
|
||||
is ContactListEvent -> consume(event)
|
||||
is DeletionEvent -> consume(event)
|
||||
is EmojiPackEvent -> consume(event)
|
||||
is EmojiPackSelectionEvent -> consume(event)
|
||||
is EmojiPackEvent -> consume(event, relay)
|
||||
is EmojiPackSelectionEvent -> consume(event, relay)
|
||||
is SealedGossipEvent -> consume(event, relay)
|
||||
|
||||
is FileHeaderEvent -> consume(event, relay)
|
||||
@ -1563,15 +1568,15 @@ object LocalCache {
|
||||
is LnZapPaymentResponseEvent -> consume(event)
|
||||
is LongTextNoteEvent -> consume(event, relay)
|
||||
is MetadataEvent -> consume(event)
|
||||
is MuteListEvent -> consume(event)
|
||||
is NNSEvent -> comsume(event)
|
||||
is MuteListEvent -> consume(event, relay)
|
||||
is NNSEvent -> comsume(event, relay)
|
||||
is PrivateDmEvent -> consume(event, relay)
|
||||
is PinListEvent -> consume(event)
|
||||
is PeopleListEvent -> consume(event)
|
||||
is PinListEvent -> consume(event, relay)
|
||||
is PeopleListEvent -> consume(event, relay)
|
||||
is PollNoteEvent -> consume(event, relay)
|
||||
is ReactionEvent -> consume(event)
|
||||
is RecommendRelayEvent -> consume(event)
|
||||
is RelaySetEvent -> consume(event)
|
||||
is RelaySetEvent -> consume(event, relay)
|
||||
is ReportEvent -> consume(event, relay)
|
||||
is RepostEvent -> {
|
||||
event.containedPost()?.let {
|
||||
|
@ -42,6 +42,7 @@ import androidx.compose.material.icons.filled.Bolt
|
||||
import androidx.compose.material.icons.filled.CurrencyBitcoin
|
||||
import androidx.compose.material.icons.filled.LocationOff
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material.icons.filled.Sell
|
||||
import androidx.compose.material.icons.filled.ShowChart
|
||||
import androidx.compose.material.icons.filled.Visibility
|
||||
import androidx.compose.material.icons.filled.VisibilityOff
|
||||
@ -93,6 +94,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.Role
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextDirection
|
||||
@ -152,6 +154,7 @@ import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||
import com.vitorpamplona.amethyst.ui.theme.replyModifier
|
||||
import com.vitorpamplona.amethyst.ui.theme.subtleBorder
|
||||
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.events.toImmutableListOfLists
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@ -345,6 +348,15 @@ fun NewPostView(
|
||||
}
|
||||
}
|
||||
|
||||
if (postViewModel.wantsProduct) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = Size5dp, horizontal = Size10dp)
|
||||
) {
|
||||
SellProduct(postViewModel = postViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
MessageField(postViewModel)
|
||||
|
||||
if (postViewModel.wantsPoll) {
|
||||
@ -530,6 +542,16 @@ fun NewPostView(
|
||||
// postViewModel.includePollHashtagInMessage(postViewModel.wantsPoll, hashtag)
|
||||
AddPollButton(postViewModel.wantsPoll) {
|
||||
postViewModel.wantsPoll = !postViewModel.wantsPoll
|
||||
if (postViewModel.wantsPoll) {
|
||||
postViewModel.wantsProduct = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AddClassifiedsButton(postViewModel) {
|
||||
postViewModel.wantsProduct = !postViewModel.wantsProduct
|
||||
if (postViewModel.wantsProduct) {
|
||||
postViewModel.wantsPoll = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -635,7 +657,11 @@ private fun MessageField(
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.what_s_on_your_mind),
|
||||
text = if (postViewModel.wantsProduct) {
|
||||
stringResource(R.string.description)
|
||||
} else {
|
||||
stringResource(R.string.what_s_on_your_mind)
|
||||
},
|
||||
color = MaterialTheme.colorScheme.placeholderText
|
||||
)
|
||||
},
|
||||
@ -776,6 +802,227 @@ fun SendDirectMessageTo(postViewModel: NewPostViewModel) {
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun SellProduct(postViewModel: NewPostViewModel) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.classifieds_title),
|
||||
fontSize = Font14SP,
|
||||
fontWeight = FontWeight.W500
|
||||
)
|
||||
|
||||
MyTextField(
|
||||
value = postViewModel.title,
|
||||
onValueChange = {
|
||||
postViewModel.title = it
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.classifieds_title_placeholder),
|
||||
color = MaterialTheme.colorScheme.placeholderText
|
||||
)
|
||||
},
|
||||
visualTransformation = UrlUserTagTransformation(
|
||||
MaterialTheme.colorScheme.primary
|
||||
),
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
focusedBorderColor = Color.Transparent
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.classifieds_price),
|
||||
fontSize = Font14SP,
|
||||
fontWeight = FontWeight.W500
|
||||
)
|
||||
|
||||
MyTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = postViewModel.price,
|
||||
onValueChange = {
|
||||
runCatching {
|
||||
if (it.text.isEmpty()) {
|
||||
postViewModel.price = TextFieldValue("")
|
||||
} else if (it.text.toLongOrNull() != null) {
|
||||
postViewModel.price = it
|
||||
}
|
||||
}
|
||||
},
|
||||
placeholder = {
|
||||
Text(
|
||||
text = "1000",
|
||||
color = MaterialTheme.colorScheme.placeholderText
|
||||
)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Number
|
||||
),
|
||||
singleLine = true,
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
focusedBorderColor = Color.Transparent
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.classifieds_condition),
|
||||
fontSize = Font14SP,
|
||||
fontWeight = FontWeight.W500
|
||||
)
|
||||
|
||||
val conditionTypes = listOf(
|
||||
Triple(ClassifiedsEvent.CONDITION.NEW, stringResource(id = R.string.classifieds_condition_new), stringResource(id = R.string.classifieds_condition_new_explainer)),
|
||||
Triple(ClassifiedsEvent.CONDITION.USED_LIKE_NEW, stringResource(id = R.string.classifieds_condition_like_new), stringResource(id = R.string.classifieds_condition_like_new_explainer)),
|
||||
Triple(ClassifiedsEvent.CONDITION.USED_GOOD, stringResource(id = R.string.classifieds_condition_good), stringResource(id = R.string.classifieds_condition_good_explainer)),
|
||||
Triple(ClassifiedsEvent.CONDITION.USED_FAIR, stringResource(id = R.string.classifieds_condition_fair), stringResource(id = R.string.classifieds_condition_fair_explainer))
|
||||
)
|
||||
|
||||
val conditionOptions = remember { conditionTypes.map { TitleExplainer(it.second, it.third) }.toImmutableList() }
|
||||
|
||||
TextSpinner(
|
||||
placeholder = conditionTypes.filter { it.first == postViewModel.condition }.first().second,
|
||||
options = conditionOptions,
|
||||
onSelect = {
|
||||
postViewModel.condition = conditionTypes[it].first
|
||||
},
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 5.dp, bottom = 1.dp)
|
||||
) { currentOption, modifier ->
|
||||
MyTextField(
|
||||
value = TextFieldValue(currentOption),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
modifier = modifier,
|
||||
singleLine = true,
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
focusedBorderColor = Color.Transparent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.classifieds_category),
|
||||
fontSize = Font14SP,
|
||||
fontWeight = FontWeight.W500
|
||||
)
|
||||
|
||||
val categoryList = listOf(
|
||||
R.string.classifieds_category_clothing,
|
||||
R.string.classifieds_category_accessories,
|
||||
R.string.classifieds_category_electronics,
|
||||
R.string.classifieds_category_furniture,
|
||||
R.string.classifieds_category_collectibles,
|
||||
R.string.classifieds_category_books,
|
||||
R.string.classifieds_category_pets,
|
||||
R.string.classifieds_category_sports,
|
||||
R.string.classifieds_category_fitness,
|
||||
R.string.classifieds_category_art,
|
||||
R.string.classifieds_category_crafts,
|
||||
R.string.classifieds_category_home,
|
||||
R.string.classifieds_category_office,
|
||||
R.string.classifieds_category_food,
|
||||
R.string.classifieds_category_misc,
|
||||
R.string.classifieds_category_other
|
||||
)
|
||||
|
||||
val categoryTypes = categoryList.map {
|
||||
Triple(it, stringResource(id = it), null)
|
||||
}
|
||||
|
||||
val categoryOptions = remember { categoryTypes.map { TitleExplainer(it.second, null) }.toImmutableList() }
|
||||
TextSpinner(
|
||||
placeholder = categoryTypes.filter { it.second == postViewModel.category.text }.firstOrNull()?.second ?: "",
|
||||
options = categoryOptions,
|
||||
onSelect = {
|
||||
postViewModel.category = TextFieldValue(categoryTypes[it].second)
|
||||
},
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 5.dp, bottom = 1.dp)
|
||||
) { currentOption, modifier ->
|
||||
MyTextField(
|
||||
value = TextFieldValue(currentOption),
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
modifier = modifier,
|
||||
singleLine = true,
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
focusedBorderColor = Color.Transparent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Divider()
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.classifieds_location),
|
||||
fontSize = Font14SP,
|
||||
fontWeight = FontWeight.W500
|
||||
)
|
||||
|
||||
MyTextField(
|
||||
value = postViewModel.locationText,
|
||||
onValueChange = {
|
||||
postViewModel.locationText = it
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
placeholder = {
|
||||
Text(
|
||||
text = stringResource(R.string.classifieds_location_placeholder),
|
||||
color = MaterialTheme.colorScheme.placeholderText
|
||||
)
|
||||
},
|
||||
visualTransformation = UrlUserTagTransformation(
|
||||
MaterialTheme.colorScheme.primary
|
||||
),
|
||||
colors = OutlinedTextFieldDefaults.colors(
|
||||
unfocusedBorderColor = Color.Transparent,
|
||||
focusedBorderColor = Color.Transparent
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FowardZapTo(postViewModel: NewPostViewModel, accountViewModel: AccountViewModel) {
|
||||
Column(
|
||||
@ -1213,6 +1460,36 @@ private fun ForwardZapTo(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddClassifiedsButton(
|
||||
postViewModel: NewPostViewModel,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
IconButton(
|
||||
onClick = {
|
||||
onClick()
|
||||
}
|
||||
) {
|
||||
if (!postViewModel.wantsProduct) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Sell,
|
||||
contentDescription = stringResource(R.string.classifieds),
|
||||
modifier = Modifier
|
||||
.size(20.dp),
|
||||
tint = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Sell,
|
||||
contentDescription = stringResource(id = R.string.classifieds),
|
||||
modifier = Modifier
|
||||
.size(20.dp),
|
||||
tint = BitcoinOrange
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MarkAsSensitive(
|
||||
postViewModel: NewPostViewModel,
|
||||
|
@ -33,7 +33,9 @@ import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.events.AddressableEvent
|
||||
import com.vitorpamplona.quartz.events.BaseTextNoteEvent
|
||||
import com.vitorpamplona.quartz.events.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.events.ClassifiedsEvent
|
||||
import com.vitorpamplona.quartz.events.CommunityDefinitionEvent
|
||||
import com.vitorpamplona.quartz.events.Price
|
||||
import com.vitorpamplona.quartz.events.PrivateDmEvent
|
||||
import com.vitorpamplona.quartz.events.TextNoteEvent
|
||||
import com.vitorpamplona.quartz.events.ZapSplitSetup
|
||||
@ -94,6 +96,14 @@ open class NewPostViewModel() : ViewModel() {
|
||||
var isValidConsensusThreshold = mutableStateOf(true)
|
||||
var isValidClosedAt = mutableStateOf(true)
|
||||
|
||||
// Classifieds
|
||||
var wantsProduct by mutableStateOf(false)
|
||||
var title by mutableStateOf(TextFieldValue(""))
|
||||
var price by mutableStateOf(TextFieldValue(""))
|
||||
var locationText by mutableStateOf(TextFieldValue(""))
|
||||
var category by mutableStateOf(TextFieldValue(""))
|
||||
var condition by mutableStateOf<ClassifiedsEvent.CONDITION>(ClassifiedsEvent.CONDITION.USED_LIKE_NEW)
|
||||
|
||||
// Invoices
|
||||
var canAddInvoice by mutableStateOf(false)
|
||||
var wantsInvoice by mutableStateOf(false)
|
||||
@ -273,6 +283,23 @@ open class NewPostViewModel() : ViewModel() {
|
||||
relayList,
|
||||
geoHash
|
||||
)
|
||||
} else if (wantsProduct) {
|
||||
account?.sendClassifieds(
|
||||
title = title.text,
|
||||
price = Price(price.text, "SATS", null),
|
||||
condition = condition,
|
||||
message = tagger.message,
|
||||
replyTo = tagger.replyTos,
|
||||
mentions = tagger.mentions,
|
||||
location = locationText.text,
|
||||
category = category.text,
|
||||
directMentions = tagger.directMentions,
|
||||
zapReceiver = zapReceiver,
|
||||
wantsToMarkAsSensitive = wantsToMarkAsSensitive,
|
||||
zapRaiserAmount = localZapRaiserAmount,
|
||||
relayList = relayList,
|
||||
geohash = geoHash
|
||||
)
|
||||
} else {
|
||||
// adds markers
|
||||
val rootId =
|
||||
@ -338,6 +365,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||
}
|
||||
},
|
||||
onError = {
|
||||
Log.e("ImageUploader", "Failed to upload the image / video", it)
|
||||
isUploadingImage = false
|
||||
viewModelScope.launch {
|
||||
imageUploadingError.emit("Failed to upload the image / video")
|
||||
@ -381,6 +409,10 @@ open class NewPostViewModel() : ViewModel() {
|
||||
wantsZapraiser = false
|
||||
zapRaiserAmount = null
|
||||
|
||||
wantsProduct = false
|
||||
condition = ClassifiedsEvent.CONDITION.USED_LIKE_NEW
|
||||
price = TextFieldValue("")
|
||||
|
||||
wantsForwardZapTo = false
|
||||
wantsToMarkAsSensitive = false
|
||||
wantsToAddGeoHash = false
|
||||
@ -527,6 +559,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||
(!wantsZapraiser || zapRaiserAmount != null) &&
|
||||
(!wantsDirectMessage || !toUsers.text.isNullOrBlank()) &&
|
||||
(!wantsPoll || (pollOptions.values.all { it.isNotEmpty() } && isValidvalueMinimum.value && isValidvalueMaximum.value)) &&
|
||||
(!wantsProduct || (!title.text.isNullOrBlank() && !price.text.isNullOrBlank() && !category.text.isNullOrBlank())) &&
|
||||
contentToAddUrl == null
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@ 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.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
@ -43,6 +44,30 @@ fun TextSpinner(
|
||||
options: ImmutableList<TitleExplainer>,
|
||||
onSelect: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
TextSpinner(
|
||||
placeholder,
|
||||
options,
|
||||
onSelect,
|
||||
modifier
|
||||
) { currentOption, modifier ->
|
||||
OutlinedTextField(
|
||||
value = currentOption,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = { label?.let { Text(it) } },
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TextSpinner(
|
||||
placeholder: String,
|
||||
options: ImmutableList<TitleExplainer>,
|
||||
onSelect: (Int) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
mainElement: @Composable (currentOption: String, modifier: Modifier) -> Unit
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
@ -50,16 +75,16 @@ fun TextSpinner(
|
||||
var currentText by remember { mutableStateOf(placeholder) }
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
modifier = modifier,
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = currentText,
|
||||
onValueChange = {},
|
||||
readOnly = true,
|
||||
label = { label?.let { Text(it) } },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
mainElement(
|
||||
currentText,
|
||||
remember {
|
||||
Modifier
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester)
|
||||
}
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
|
@ -673,4 +673,44 @@
|
||||
<string name="send_the_seller_a_message">Send the seller a message</string>
|
||||
<string name="hi_seller_is_this_still_available">Hi %1$s, is this still available?</string>
|
||||
<string name="hi_there_is_this_still_available">Hi there, is this still available?</string>
|
||||
|
||||
<string name="classifieds">Sell an Item</string>
|
||||
|
||||
<string name="classifieds_title">Title</string>
|
||||
<string name="classifieds_title_placeholder">iPhone 13</string>
|
||||
<string name="classifieds_condition">Condition</string>
|
||||
<string name="classifieds_category">Category</string>
|
||||
<string name="classifieds_price">Price (in Sats)</string>
|
||||
<string name="classifieds_price_placeholder">1000</string>
|
||||
<string name="classifieds_location">Location</string>
|
||||
<string name="classifieds_location_placeholder">City, State, Country</string>
|
||||
|
||||
<string name="classifieds_condition_new">New</string>
|
||||
<string name="classifieds_condition_new_explainer">It\'s a brand new unit, in the original box</string>
|
||||
|
||||
<string name="classifieds_condition_like_new">Like New</string>
|
||||
<string name="classifieds_condition_like_new_explainer">It\'s used, but there are no signs of usage</string>
|
||||
|
||||
<string name="classifieds_condition_good">Good</string>
|
||||
<string name="classifieds_condition_good_explainer">It has some superficial usage marks</string>
|
||||
|
||||
<string name="classifieds_condition_fair">Fair</string>
|
||||
<string name="classifieds_condition_fair_explainer">It is still in acceptable and functional shape</string>
|
||||
|
||||
<string name="classifieds_category_clothing">Clothing</string>
|
||||
<string name="classifieds_category_accessories">Accessories</string>
|
||||
<string name="classifieds_category_electronics">Electronics</string>
|
||||
<string name="classifieds_category_furniture">Furniture</string>
|
||||
<string name="classifieds_category_collectibles">Collectibles</string>
|
||||
<string name="classifieds_category_books">Books</string>
|
||||
<string name="classifieds_category_pets">Pets</string>
|
||||
<string name="classifieds_category_sports">Sports</string>
|
||||
<string name="classifieds_category_fitness">Fitness</string>
|
||||
<string name="classifieds_category_art">Art</string>
|
||||
<string name="classifieds_category_crafts">Crafts</string>
|
||||
<string name="classifieds_category_home">Home</string>
|
||||
<string name="classifieds_category_office">Office</string>
|
||||
<string name="classifieds_category_food">Food</string>
|
||||
<string name="classifieds_category_misc">Miscellaneous</string>
|
||||
<string name="classifieds_category_other">Other</string>
|
||||
</resources>
|
||||
|
@ -19,6 +19,7 @@ class ClassifiedsEvent(
|
||||
) : BaseAddressableEvent(id, pubKey, createdAt, kind, tags, content, sig) {
|
||||
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)
|
||||
fun image() = tags.firstOrNull { it.size > 1 && it[0] == "image" }?.get(1)
|
||||
fun condition() = tags.firstOrNull { it.size > 1 && it[0] == "condition" }?.get(1)
|
||||
fun images() = tags.filter { it.size > 1 && it[0] == "image" }.map { it[1] }
|
||||
fun summary() = tags.firstOrNull { it.size > 1 && it[0] == "summary" }?.get(1)
|
||||
fun price() = tags.firstOrNull { it.size > 1 && it[0] == "price" }?.let {
|
||||
@ -32,23 +33,65 @@ class ClassifiedsEvent(
|
||||
null
|
||||
}
|
||||
|
||||
enum class CONDITION(val value: String){
|
||||
NEW("new"),
|
||||
USED_LIKE_NEW("like new"),
|
||||
USED_GOOD("good"),
|
||||
USED_FAIR("fair"),
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val kind = 30402
|
||||
private val imageExtensions = listOf("png", "jpg", "gif", "bmp", "jpeg", "webp", "svg")
|
||||
|
||||
fun create(
|
||||
dTag: String,
|
||||
title: String?,
|
||||
image: String?,
|
||||
summary: String?,
|
||||
message: String,
|
||||
price: Price?,
|
||||
location: String?,
|
||||
publishedAt: Long?,
|
||||
category: String?,
|
||||
condition: ClassifiedsEvent.CONDITION?,
|
||||
publishedAt: Long? = TimeUtils.now(),
|
||||
replyTos: List<String>?,
|
||||
addresses: List<ATag>?,
|
||||
mentions: List<String>?,
|
||||
directMentions: Set<HexKey>,
|
||||
zapReceiver: List<ZapSplitSetup>? = null,
|
||||
markAsSensitive: Boolean,
|
||||
zapRaiserAmount: Long?,
|
||||
geohash: String? = null,
|
||||
signer: NostrSigner,
|
||||
createdAt: Long = TimeUtils.now(),
|
||||
onReady: (ClassifiedsEvent) -> Unit
|
||||
) {
|
||||
val tags = mutableListOf<Array<String>>()
|
||||
|
||||
replyTos?.forEach {
|
||||
if (it in directMentions) {
|
||||
tags.add(arrayOf("e", it, "", "mention"))
|
||||
} else {
|
||||
tags.add(arrayOf("e", it))
|
||||
}
|
||||
}
|
||||
mentions?.forEach {
|
||||
if (it in directMentions) {
|
||||
tags.add(arrayOf("p", it, "", "mention"))
|
||||
} else {
|
||||
tags.add(arrayOf("p", it))
|
||||
}
|
||||
}
|
||||
addresses?.forEach {
|
||||
val aTag = it.toTag()
|
||||
if (aTag in directMentions) {
|
||||
tags.add(arrayOf("a", aTag, "", "mention"))
|
||||
} else {
|
||||
tags.add(arrayOf("a", aTag))
|
||||
}
|
||||
}
|
||||
|
||||
tags.add(arrayOf("d", dTag))
|
||||
title?.let { tags.add(arrayOf("title", it)) }
|
||||
image?.let { tags.add(arrayOf("image", it)) }
|
||||
@ -62,11 +105,36 @@ class ClassifiedsEvent(
|
||||
tags.add(arrayOf("price", it.amount))
|
||||
}
|
||||
}
|
||||
category?.let { tags.add(arrayOf("t", it)) }
|
||||
location?.let { tags.add(arrayOf("location", it)) }
|
||||
publishedAt?.let { tags.add(arrayOf("publishedAt", it.toString())) }
|
||||
title?.let { tags.add(arrayOf("title", it)) }
|
||||
condition?.let { tags.add(arrayOf("condition", it.value)) }
|
||||
|
||||
signer.sign(createdAt, kind, tags.toTypedArray(), "", onReady)
|
||||
findHashtags(message).forEach {
|
||||
tags.add(arrayOf("t", it))
|
||||
tags.add(arrayOf("t", it.lowercase()))
|
||||
}
|
||||
zapReceiver?.forEach {
|
||||
tags.add(arrayOf("zap", it.lnAddressOrPubKeyHex, it.relay ?: "", it.weight.toString()))
|
||||
}
|
||||
findURLs(message).forEach {
|
||||
val removedParamsFromUrl = it.split("?")[0].lowercase()
|
||||
if (imageExtensions.any { removedParamsFromUrl.endsWith(it) }) {
|
||||
tags.add(arrayOf("image", it))
|
||||
}
|
||||
tags.add(arrayOf("r", it))
|
||||
}
|
||||
if (markAsSensitive) {
|
||||
tags.add(arrayOf("content-warning", ""))
|
||||
}
|
||||
zapRaiserAmount?.let {
|
||||
tags.add(arrayOf("zapraiser", "$it"))
|
||||
}
|
||||
geohash?.let {
|
||||
tags.addAll(geohashMipMap(it))
|
||||
}
|
||||
|
||||
signer.sign(createdAt, kind, tags.toTypedArray(), message, onReady)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user