kopia lustrzana https://github.com/vitorpamplona/amethyst
Adds a Share option with NIP-19 nprofile icon on the User Profile screen
Moves to NProfile instead of NPub to cite users. Adds Npub or NIP-05 to the QR Screenpull/938/head
rodzic
35a9c07636
commit
accad0c77a
|
@ -241,33 +241,25 @@ object LocalCache {
|
|||
return users.get(key)
|
||||
}
|
||||
|
||||
fun getAddressableNoteIfExists(key: String): AddressableNote? {
|
||||
return addressables.get(key)
|
||||
}
|
||||
fun getAddressableNoteIfExists(key: String): AddressableNote? = addressables.get(key)
|
||||
|
||||
fun getNoteIfExists(key: String): Note? {
|
||||
return addressables.get(key) ?: notes.get(key)
|
||||
}
|
||||
fun getNoteIfExists(key: String): Note? = addressables.get(key) ?: notes.get(key)
|
||||
|
||||
fun getChannelIfExists(key: String): Channel? {
|
||||
return channels.get(key)
|
||||
}
|
||||
fun getChannelIfExists(key: String): Channel? = channels.get(key)
|
||||
|
||||
fun getNoteIfExists(event: Event): Note? {
|
||||
return if (event is AddressableEvent) {
|
||||
fun getNoteIfExists(event: Event): Note? =
|
||||
if (event is AddressableEvent) {
|
||||
getAddressableNoteIfExists(event.addressTag())
|
||||
} else {
|
||||
getNoteIfExists(event.id)
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrCreateNote(event: Event): Note {
|
||||
return if (event is AddressableEvent) {
|
||||
fun getOrCreateNote(event: Event): Note =
|
||||
if (event is AddressableEvent) {
|
||||
getOrCreateAddressableNote(event.address())
|
||||
} else {
|
||||
getOrCreateNote(event.id)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkGetOrCreateNote(key: String): Note? {
|
||||
checkNotInMainThread()
|
||||
|
@ -348,8 +340,8 @@ object LocalCache {
|
|||
return HexValidator.isHex(key)
|
||||
}
|
||||
|
||||
fun checkGetOrCreateAddressableNote(key: String): AddressableNote? {
|
||||
return try {
|
||||
fun checkGetOrCreateAddressableNote(key: String): AddressableNote? =
|
||||
try {
|
||||
val addr = ATag.parse(key, null) // relay doesn't matter for the index.
|
||||
if (addr != null) {
|
||||
getOrCreateAddressableNote(addr)
|
||||
|
@ -360,7 +352,6 @@ object LocalCache {
|
|||
Log.e("LocalCache", "Invalid Key to create channel: $key", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrCreateAddressableNoteInternal(key: ATag): AddressableNote {
|
||||
// checkNotInMainThread()
|
||||
|
@ -395,6 +386,10 @@ object LocalCache {
|
|||
val newUserMetadata = event.contactMetaData()
|
||||
if (newUserMetadata != null) {
|
||||
oldUser.updateUserInfo(newUserMetadata, event)
|
||||
if (relay != null) {
|
||||
oldUser.addRelayBeingUsed(relay, event.createdAt)
|
||||
oldUser.latestMetadataRelay = relay.url
|
||||
}
|
||||
}
|
||||
// Log.d("MT", "New User Metadata ${oldUser.pubkeyDisplayHex()} ${oldUser.toBestDisplayName()} from ${relay?.url}")
|
||||
} else {
|
||||
|
@ -428,11 +423,11 @@ object LocalCache {
|
|||
}
|
||||
}
|
||||
|
||||
fun formattedDateTime(timestamp: Long): String {
|
||||
return Instant.ofEpochSecond(timestamp)
|
||||
fun formattedDateTime(timestamp: Long): String =
|
||||
Instant
|
||||
.ofEpochSecond(timestamp)
|
||||
.atZone(ZoneId.systemDefault())
|
||||
.format(DateTimeFormatter.ofPattern("uuuu MMM d hh:mm a"))
|
||||
}
|
||||
|
||||
fun consume(
|
||||
event: TextNoteEvent,
|
||||
|
@ -755,8 +750,8 @@ object LocalCache {
|
|||
}
|
||||
}
|
||||
|
||||
fun computeReplyTo(event: Event): List<Note> {
|
||||
return when (event) {
|
||||
fun computeReplyTo(event: Event): List<Note> =
|
||||
when (event) {
|
||||
is PollNoteEvent -> event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
|
||||
is WikiNoteEvent -> event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
|
||||
is LongTextNoteEvent -> event.tagsWithoutCitations().mapNotNull { checkGetOrCreateNote(it) }
|
||||
|
@ -805,7 +800,6 @@ object LocalCache {
|
|||
|
||||
else -> emptyList<Note>()
|
||||
}
|
||||
}
|
||||
|
||||
fun consume(
|
||||
event: PollNoteEvent,
|
||||
|
@ -1218,7 +1212,8 @@ object LocalCache {
|
|||
if (deletionIndex.add(event)) {
|
||||
var deletedAtLeastOne = false
|
||||
|
||||
event.deleteEvents()
|
||||
event
|
||||
.deleteEvents()
|
||||
.mapNotNull { getNoteIfExists(it) }
|
||||
.forEach { deleteNote ->
|
||||
// must be the same author
|
||||
|
@ -2030,16 +2025,16 @@ object LocalCache {
|
|||
suspend fun findStatusesForUser(user: User): ImmutableList<AddressableNote> {
|
||||
checkNotInMainThread()
|
||||
|
||||
return addressables.filter { _, it ->
|
||||
val noteEvent = it.event
|
||||
(
|
||||
noteEvent is StatusEvent &&
|
||||
noteEvent.pubKey == user.pubkeyHex &&
|
||||
!noteEvent.isExpired() &&
|
||||
noteEvent.content.isNotBlank()
|
||||
)
|
||||
}
|
||||
.sortedWith(compareBy({ it.event?.expiration() ?: it.event?.createdAt() }, { it.idHex }))
|
||||
return addressables
|
||||
.filter { _, it ->
|
||||
val noteEvent = it.event
|
||||
(
|
||||
noteEvent is StatusEvent &&
|
||||
noteEvent.pubKey == user.pubkeyHex &&
|
||||
!noteEvent.isExpired() &&
|
||||
noteEvent.content.isNotBlank()
|
||||
)
|
||||
}.sortedWith(compareBy({ it.event?.expiration() ?: it.event?.createdAt() }, { it.idHex }))
|
||||
.reversed()
|
||||
.toImmutableList()
|
||||
}
|
||||
|
@ -2066,9 +2061,7 @@ object LocalCache {
|
|||
|
||||
val modificationCache = LruCache<HexKey, List<Note>>(20)
|
||||
|
||||
fun cachedModificationEventsForNote(note: Note): List<Note>? {
|
||||
return modificationCache[note.idHex]
|
||||
}
|
||||
fun cachedModificationEventsForNote(note: Note): List<Note>? = modificationCache[note.idHex]
|
||||
|
||||
suspend fun findLatestModificationForNote(note: Note): List<Note> {
|
||||
checkNotInMainThread()
|
||||
|
@ -2082,11 +2075,12 @@ object LocalCache {
|
|||
val time = TimeUtils.now()
|
||||
|
||||
val newNotes =
|
||||
notes.filter { _, item ->
|
||||
val noteEvent = item.event
|
||||
notes
|
||||
.filter { _, item ->
|
||||
val noteEvent = item.event
|
||||
|
||||
noteEvent is TextNoteModificationEvent && noteEvent.pubKey == originalAuthor && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time)
|
||||
}.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
||||
noteEvent is TextNoteModificationEvent && noteEvent.pubKey == originalAuthor && noteEvent.isTaggedEvent(note.idHex) && !noteEvent.isExpirationBefore(time)
|
||||
}.sortedWith(compareBy({ it.createdAt() }, { it.idHex }))
|
||||
|
||||
modificationCache.put(note.idHex, newNotes)
|
||||
|
||||
|
@ -2210,9 +2204,11 @@ object LocalCache {
|
|||
note.event is GenericRepostEvent
|
||||
) &&
|
||||
note.replyTo?.any { it.liveSet?.isInUse() == true } != true &&
|
||||
note.liveSet?.isInUse() != true && // don't delete if observing.
|
||||
note.liveSet?.isInUse() != true &&
|
||||
// don't delete if observing.
|
||||
note.author?.pubkeyHex !in
|
||||
accounts && // don't delete if it is the logged in account
|
||||
accounts &&
|
||||
// don't delete if it is the logged in account
|
||||
note.event?.isTaggedUsers(accounts) !=
|
||||
true // don't delete if it's a notification to the logged in user
|
||||
}
|
||||
|
@ -2308,8 +2304,7 @@ object LocalCache {
|
|||
?.hiddenUsers
|
||||
?.map { userHex ->
|
||||
(notes.filter { _, it -> it.event?.pubKey() == userHex } + addressables.filter { _, it -> it.event?.pubKey() == userHex }).toSet()
|
||||
}
|
||||
?.flatten()
|
||||
}?.flatten()
|
||||
?: emptyList()
|
||||
|
||||
toBeRemoved.forEach {
|
||||
|
@ -2596,8 +2591,8 @@ object LocalCache {
|
|||
}
|
||||
}
|
||||
|
||||
fun hasConsumed(notificationEvent: Event): Boolean {
|
||||
return if (notificationEvent is AddressableEvent) {
|
||||
fun hasConsumed(notificationEvent: Event): Boolean =
|
||||
if (notificationEvent is AddressableEvent) {
|
||||
val note = addressables.get(notificationEvent.addressTag())
|
||||
val noteEvent = note?.event
|
||||
noteEvent != null && notificationEvent.createdAt <= noteEvent.createdAt()
|
||||
|
@ -2605,7 +2600,6 @@ object LocalCache {
|
|||
val note = notes.get(notificationEvent.id)
|
||||
note?.event != null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Stable
|
||||
|
@ -2617,8 +2611,7 @@ class LocalCacheLiveData {
|
|||
private val bundler = BundledInsert<Note>(1000, Dispatchers.IO)
|
||||
|
||||
fun invalidateData(newNote: Note) {
|
||||
bundler.invalidateList(newNote) {
|
||||
bundledNewNotes ->
|
||||
bundler.invalidateList(newNote) { bundledNewNotes ->
|
||||
_newEventBundles.emit(bundledNewNotes)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,9 @@ import com.vitorpamplona.amethyst.ui.note.toShortenHex
|
|||
import com.vitorpamplona.quartz.encoders.Hex
|
||||
import com.vitorpamplona.quartz.encoders.HexKey
|
||||
import com.vitorpamplona.quartz.encoders.Lud06
|
||||
import com.vitorpamplona.quartz.encoders.Nip19Bech32
|
||||
import com.vitorpamplona.quartz.encoders.toNpub
|
||||
import com.vitorpamplona.quartz.events.AdvertisedRelayListEvent
|
||||
import com.vitorpamplona.quartz.events.BookmarkListEvent
|
||||
import com.vitorpamplona.quartz.events.ChatroomKey
|
||||
import com.vitorpamplona.quartz.events.ContactListEvent
|
||||
|
@ -49,10 +51,13 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||
import java.math.BigDecimal
|
||||
|
||||
@Stable
|
||||
class User(val pubkeyHex: String) {
|
||||
class User(
|
||||
val pubkeyHex: String,
|
||||
) {
|
||||
var info: UserMetadata? = null
|
||||
|
||||
var latestMetadata: MetadataEvent? = null
|
||||
var latestMetadataRelay: String? = null
|
||||
var latestContactList: ContactListEvent? = null
|
||||
var latestBookmarkList: BookmarkListEvent? = null
|
||||
|
||||
|
@ -76,7 +81,16 @@ class User(val pubkeyHex: String) {
|
|||
|
||||
fun pubkeyDisplayHex() = pubkeyNpub().toShortenHex()
|
||||
|
||||
fun toNostrUri() = "nostr:${pubkeyNpub()}"
|
||||
fun toNProfile(): String {
|
||||
val relayList = (LocalCache.getAddressableNoteIfExists(AdvertisedRelayListEvent.createAddressTag(pubkeyHex))?.event as? AdvertisedRelayListEvent)?.writeRelays()
|
||||
|
||||
return Nip19Bech32.createNProfile(
|
||||
pubkeyHex,
|
||||
relayList?.take(3) ?: listOfNotNull(latestMetadataRelay),
|
||||
)
|
||||
}
|
||||
|
||||
fun toNostrUri() = "nostr:${toNProfile()}"
|
||||
|
||||
override fun toString(): String = pubkeyHex
|
||||
|
||||
|
@ -96,17 +110,11 @@ class User(val pubkeyHex: String) {
|
|||
return firstName
|
||||
}
|
||||
|
||||
fun toBestDisplayName(): String {
|
||||
return info?.bestName() ?: pubkeyDisplayHex()
|
||||
}
|
||||
fun toBestDisplayName(): String = info?.bestName() ?: pubkeyDisplayHex()
|
||||
|
||||
fun nip05(): String? {
|
||||
return info?.nip05
|
||||
}
|
||||
fun nip05(): String? = info?.nip05
|
||||
|
||||
fun profilePicture(): String? {
|
||||
return info?.picture
|
||||
}
|
||||
fun profilePicture(): String? = info?.picture
|
||||
|
||||
fun updateBookmark(event: BookmarkListEvent) {
|
||||
if (event.id == latestBookmarkList?.id) return
|
||||
|
@ -132,10 +140,18 @@ class User(val pubkeyHex: String) {
|
|||
// Update Followers of the past user list
|
||||
// Update Followers of the new contact list
|
||||
(oldContactListEvent)?.unverifiedFollowKeySet()?.forEach {
|
||||
LocalCache.getUserIfExists(it)?.liveSet?.innerFollowers?.invalidateData()
|
||||
LocalCache
|
||||
.getUserIfExists(it)
|
||||
?.liveSet
|
||||
?.innerFollowers
|
||||
?.invalidateData()
|
||||
}
|
||||
(latestContactList)?.unverifiedFollowKeySet()?.forEach {
|
||||
LocalCache.getUserIfExists(it)?.liveSet?.innerFollowers?.invalidateData()
|
||||
LocalCache
|
||||
.getUserIfExists(it)
|
||||
?.liveSet
|
||||
?.innerFollowers
|
||||
?.invalidateData()
|
||||
}
|
||||
|
||||
liveSet?.innerRelays?.invalidateData()
|
||||
|
@ -198,25 +214,19 @@ class User(val pubkeyHex: String) {
|
|||
return amount
|
||||
}
|
||||
|
||||
fun reportsBy(user: User): Set<Note> {
|
||||
return reports[user] ?: emptySet()
|
||||
}
|
||||
fun reportsBy(user: User): Set<Note> = reports[user] ?: emptySet()
|
||||
|
||||
fun countReportAuthorsBy(users: Set<HexKey>): Int {
|
||||
return reports.count { it.key.pubkeyHex in users }
|
||||
}
|
||||
fun countReportAuthorsBy(users: Set<HexKey>): Int = reports.count { it.key.pubkeyHex in users }
|
||||
|
||||
fun reportsBy(users: Set<HexKey>): List<Note> {
|
||||
return reports
|
||||
fun reportsBy(users: Set<HexKey>): List<Note> =
|
||||
reports
|
||||
.mapNotNull {
|
||||
if (it.key.pubkeyHex in users) {
|
||||
it.value
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.flatten()
|
||||
}
|
||||
}.flatten()
|
||||
|
||||
@Synchronized
|
||||
private fun getOrCreatePrivateChatroomSync(key: ChatroomKey): Chatroom {
|
||||
|
@ -235,9 +245,7 @@ class User(val pubkeyHex: String) {
|
|||
return getOrCreatePrivateChatroom(key)
|
||||
}
|
||||
|
||||
private fun getOrCreatePrivateChatroom(key: ChatroomKey): Chatroom {
|
||||
return privateChatrooms[key] ?: getOrCreatePrivateChatroomSync(key)
|
||||
}
|
||||
private fun getOrCreatePrivateChatroom(key: ChatroomKey): Chatroom = privateChatrooms[key] ?: getOrCreatePrivateChatroomSync(key)
|
||||
|
||||
fun addMessage(
|
||||
room: ChatroomKey,
|
||||
|
@ -326,13 +334,9 @@ class User(val pubkeyHex: String) {
|
|||
liveSet?.innerMetadata?.invalidateData()
|
||||
}
|
||||
|
||||
fun isFollowing(user: User): Boolean {
|
||||
return latestContactList?.isTaggedUser(user.pubkeyHex) ?: false
|
||||
}
|
||||
fun isFollowing(user: User): Boolean = latestContactList?.isTaggedUser(user.pubkeyHex) ?: false
|
||||
|
||||
fun isFollowingHashtag(tag: String): Boolean {
|
||||
return latestContactList?.isTaggedHash(tag) ?: false
|
||||
}
|
||||
fun isFollowingHashtag(tag: String): Boolean = latestContactList?.isTaggedHash(tag) ?: false
|
||||
|
||||
fun isFollowingHashtagCached(tag: String): Boolean {
|
||||
return latestContactList?.verifiedFollowTagSet?.let {
|
||||
|
@ -362,37 +366,21 @@ class User(val pubkeyHex: String) {
|
|||
?: false
|
||||
}
|
||||
|
||||
fun transientFollowCount(): Int? {
|
||||
return latestContactList?.unverifiedFollowKeySet()?.size
|
||||
}
|
||||
fun transientFollowCount(): Int? = latestContactList?.unverifiedFollowKeySet()?.size
|
||||
|
||||
suspend fun transientFollowerCount(): Int {
|
||||
return LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
|
||||
}
|
||||
suspend fun transientFollowerCount(): Int = LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
|
||||
|
||||
fun cachedFollowingKeySet(): Set<HexKey> {
|
||||
return latestContactList?.verifiedFollowKeySet ?: emptySet()
|
||||
}
|
||||
fun cachedFollowingKeySet(): Set<HexKey> = latestContactList?.verifiedFollowKeySet ?: emptySet()
|
||||
|
||||
fun cachedFollowingTagSet(): Set<String> {
|
||||
return latestContactList?.verifiedFollowTagSet ?: emptySet()
|
||||
}
|
||||
fun cachedFollowingTagSet(): Set<String> = latestContactList?.verifiedFollowTagSet ?: emptySet()
|
||||
|
||||
fun cachedFollowingGeohashSet(): Set<HexKey> {
|
||||
return latestContactList?.verifiedFollowGeohashSet ?: emptySet()
|
||||
}
|
||||
fun cachedFollowingGeohashSet(): Set<HexKey> = latestContactList?.verifiedFollowGeohashSet ?: emptySet()
|
||||
|
||||
fun cachedFollowingCommunitiesSet(): Set<HexKey> {
|
||||
return latestContactList?.verifiedFollowCommunitySet ?: emptySet()
|
||||
}
|
||||
fun cachedFollowingCommunitiesSet(): Set<HexKey> = latestContactList?.verifiedFollowCommunitySet ?: emptySet()
|
||||
|
||||
fun cachedFollowCount(): Int? {
|
||||
return latestContactList?.verifiedFollowKeySet?.size
|
||||
}
|
||||
fun cachedFollowCount(): Int? = latestContactList?.verifiedFollowKeySet?.size
|
||||
|
||||
suspend fun cachedFollowerCount(): Int {
|
||||
return LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
|
||||
}
|
||||
suspend fun cachedFollowerCount(): Int = LocalCache.users.count { _, it -> it.latestContactList?.isTaggedUser(pubkeyHex) ?: false }
|
||||
|
||||
fun hasSentMessagesTo(key: ChatroomKey?): Boolean {
|
||||
val messagesToUser = privateChatrooms[key] ?: return false
|
||||
|
@ -403,16 +391,13 @@ class User(val pubkeyHex: String) {
|
|||
fun hasReport(
|
||||
loggedIn: User,
|
||||
type: ReportEvent.ReportType,
|
||||
): Boolean {
|
||||
return reports[loggedIn]?.firstOrNull {
|
||||
): Boolean =
|
||||
reports[loggedIn]?.firstOrNull {
|
||||
it.event is ReportEvent &&
|
||||
(it.event as ReportEvent).reportedAuthor().any { it.reportType == type }
|
||||
} != null
|
||||
}
|
||||
|
||||
fun anyNameStartsWith(username: String): Boolean {
|
||||
return info?.anyNameStartsWith(username) ?: false
|
||||
}
|
||||
fun anyNameStartsWith(username: String): Boolean = info?.anyNameStartsWith(username) ?: false
|
||||
|
||||
var liveSet: UserLiveSet? = null
|
||||
var flowSet: UserFlowSet? = null
|
||||
|
@ -473,14 +458,14 @@ class User(val pubkeyHex: String) {
|
|||
}
|
||||
|
||||
@Stable
|
||||
class UserFlowSet(u: User) {
|
||||
class UserFlowSet(
|
||||
u: User,
|
||||
) {
|
||||
// Observers line up here.
|
||||
val follows = UserBundledRefresherFlow(u)
|
||||
val relays = UserBundledRefresherFlow(u)
|
||||
|
||||
fun isInUse(): Boolean {
|
||||
return relays.stateFlow.subscriptionCount.value > 0 || follows.stateFlow.subscriptionCount.value > 0
|
||||
}
|
||||
fun isInUse(): Boolean = relays.stateFlow.subscriptionCount.value > 0 || follows.stateFlow.subscriptionCount.value > 0
|
||||
|
||||
fun destroy() {
|
||||
relays.destroy()
|
||||
|
@ -489,7 +474,9 @@ class UserFlowSet(u: User) {
|
|||
}
|
||||
|
||||
@Stable
|
||||
class UserLiveSet(u: User) {
|
||||
class UserLiveSet(
|
||||
u: User,
|
||||
) {
|
||||
val innerMetadata = UserBundledRefresherLiveData(u)
|
||||
|
||||
// UI Observers line up here.
|
||||
|
@ -521,8 +508,8 @@ class UserLiveSet(u: User) {
|
|||
|
||||
val userMetadataInfo = innerMetadata.map { it.user.info }.distinctUntilChanged()
|
||||
|
||||
fun isInUse(): Boolean {
|
||||
return metadata.hasObservers() ||
|
||||
fun isInUse(): Boolean =
|
||||
metadata.hasObservers() ||
|
||||
follows.hasObservers() ||
|
||||
followers.hasObservers() ||
|
||||
reports.hasObservers() ||
|
||||
|
@ -535,7 +522,6 @@ class UserLiveSet(u: User) {
|
|||
profilePictureChanges.hasObservers() ||
|
||||
nip05Changes.hasObservers() ||
|
||||
userMetadataInfo.hasObservers()
|
||||
}
|
||||
|
||||
fun destroy() {
|
||||
innerMetadata.destroy()
|
||||
|
@ -558,7 +544,9 @@ data class RelayInfo(
|
|||
var counter: Long,
|
||||
)
|
||||
|
||||
class UserBundledRefresherLiveData(val user: User) : LiveData<UserState>(UserState(user)) {
|
||||
class UserBundledRefresherLiveData(
|
||||
val user: User,
|
||||
) : LiveData<UserState>(UserState(user)) {
|
||||
// Refreshes observers in batches.
|
||||
private val bundler = BundledUpdate(500, Dispatchers.IO)
|
||||
|
||||
|
@ -585,7 +573,9 @@ class UserBundledRefresherLiveData(val user: User) : LiveData<UserState>(UserSta
|
|||
}
|
||||
|
||||
@Stable
|
||||
class UserBundledRefresherFlow(val user: User) {
|
||||
class UserBundledRefresherFlow(
|
||||
val user: User,
|
||||
) {
|
||||
// Refreshes observers in batches.
|
||||
private val bundler = BundledUpdate(500, Dispatchers.IO)
|
||||
val stateFlow = MutableStateFlow(UserState(user))
|
||||
|
@ -605,7 +595,10 @@ class UserBundledRefresherFlow(val user: User) {
|
|||
}
|
||||
}
|
||||
|
||||
class UserLoadingLiveData<Y>(val user: User, initialValue: Y?) : MediatorLiveData<Y>(initialValue) {
|
||||
class UserLoadingLiveData<Y>(
|
||||
val user: User,
|
||||
initialValue: Y?,
|
||||
) : MediatorLiveData<Y>(initialValue) {
|
||||
override fun onActive() {
|
||||
super.onActive()
|
||||
NostrSingleUserDataSource.add(user)
|
||||
|
@ -617,4 +610,6 @@ class UserLoadingLiveData<Y>(val user: User, initialValue: Y?) : MediatorLiveDat
|
|||
}
|
||||
}
|
||||
|
||||
@Immutable class UserState(val user: User)
|
||||
@Immutable class UserState(
|
||||
val user: User,
|
||||
)
|
||||
|
|
|
@ -100,10 +100,10 @@ class NewMessageTagger(
|
|||
val results = parseDirtyWordForKey(word)
|
||||
when (val entity = results?.key?.entity) {
|
||||
is Nip19Bech32.NPub -> {
|
||||
getNostrAddress(dao.getOrCreateUser(entity.hex).pubkeyNpub(), results.restOfWord)
|
||||
getNostrAddress(dao.getOrCreateUser(entity.hex).toNProfile(), results.restOfWord)
|
||||
}
|
||||
is Nip19Bech32.NProfile -> {
|
||||
getNostrAddress(dao.getOrCreateUser(entity.hex).pubkeyNpub(), results.restOfWord)
|
||||
getNostrAddress(dao.getOrCreateUser(entity.hex).toNProfile(), results.restOfWord)
|
||||
}
|
||||
|
||||
is Nip19Bech32.Note -> {
|
||||
|
@ -138,17 +138,15 @@ class NewMessageTagger(
|
|||
word
|
||||
}
|
||||
}
|
||||
}
|
||||
.joinToString(" ")
|
||||
}
|
||||
.joinToString("\n")
|
||||
}.joinToString(" ")
|
||||
}.joinToString("\n")
|
||||
}
|
||||
|
||||
fun getNostrAddress(
|
||||
bechAddress: String,
|
||||
restOfTheWord: String?,
|
||||
): String {
|
||||
return if (restOfTheWord.isNullOrEmpty()) {
|
||||
): String =
|
||||
if (restOfTheWord.isNullOrEmpty()) {
|
||||
"nostr:$bechAddress"
|
||||
} else {
|
||||
if (Bech32.ALPHABET.contains(restOfTheWord.get(0), true)) {
|
||||
|
@ -157,9 +155,11 @@ class NewMessageTagger(
|
|||
"nostr:${bechAddress}$restOfTheWord"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable data class DirtyKeyInfo(val key: Nip19Bech32.ParseReturn, val restOfWord: String?)
|
||||
@Immutable data class DirtyKeyInfo(
|
||||
val key: Nip19Bech32.ParseReturn,
|
||||
val restOfWord: String?,
|
||||
)
|
||||
|
||||
fun parseDirtyWordForKey(mightBeAKey: String): DirtyKeyInfo? {
|
||||
var key = mightBeAKey
|
||||
|
|
|
@ -168,8 +168,7 @@ fun DrawerContent(
|
|||
BottomContent(
|
||||
accountViewModel.account.userProfile(),
|
||||
drawerState,
|
||||
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
||||
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
||||
accountViewModel,
|
||||
nav,
|
||||
)
|
||||
}
|
||||
|
@ -741,8 +740,7 @@ fun IconRowRelays(
|
|||
fun BottomContent(
|
||||
user: User,
|
||||
drawerState: DrawerState,
|
||||
loadProfilePicture: Boolean,
|
||||
loadRobohash: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
nav: (String) -> Unit,
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
@ -800,8 +798,7 @@ fun BottomContent(
|
|||
if (dialogOpen) {
|
||||
ShowQRDialog(
|
||||
user,
|
||||
loadProfilePicture = loadProfilePicture,
|
||||
loadRobohash = loadRobohash,
|
||||
accountViewModel,
|
||||
onScan = {
|
||||
dialogOpen = false
|
||||
coroutineScope.launch { drawerState.close() }
|
||||
|
|
|
@ -310,7 +310,7 @@ fun DisplayStatus(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun DisplayNIP05(
|
||||
fun DisplayNIP05(
|
||||
nip05: String,
|
||||
nip05Verified: MutableState<Boolean?>,
|
||||
accountViewModel: AccountViewModel,
|
||||
|
|
|
@ -84,6 +84,7 @@ import androidx.core.graphics.ColorUtils
|
|||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||
import com.vitorpamplona.amethyst.model.Note
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.actions.NewPostView
|
||||
import com.vitorpamplona.amethyst.ui.components.SelectTextDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
|
@ -95,7 +96,6 @@ import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground
|
|||
import com.vitorpamplona.quartz.events.AudioTrackEvent
|
||||
import com.vitorpamplona.quartz.events.FileHeaderEvent
|
||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private fun lightenColor(
|
||||
|
@ -110,6 +110,10 @@ private fun lightenColor(
|
|||
return Color(argb)
|
||||
}
|
||||
|
||||
val externalLinkForUser = { user: User ->
|
||||
"https://njump.me/${user.toNProfile()}"
|
||||
}
|
||||
|
||||
val externalLinkForNote = { note: Note ->
|
||||
if (note is AddressableNote) {
|
||||
if (note.event?.getReward() != null) {
|
||||
|
@ -298,10 +302,12 @@ private fun RenderMainPopup(
|
|||
Icons.Default.AlternateEmail,
|
||||
stringRes(R.string.quick_action_copy_user_id),
|
||||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
clipboardManager.setText(AnnotatedString("nostr:${note.author?.pubkeyNpub()}"))
|
||||
showToast(R.string.copied_user_id_to_clipboard)
|
||||
onDismiss()
|
||||
note.author?.let {
|
||||
scope.launch {
|
||||
clipboardManager.setText(AnnotatedString(it.toNostrUri()))
|
||||
showToast(R.string.copied_user_id_to_clipboard)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
VerticalDivider(color = primaryLight)
|
||||
|
@ -309,8 +315,8 @@ private fun RenderMainPopup(
|
|||
Icons.Default.FormatQuote,
|
||||
stringRes(R.string.quick_action_copy_note_id),
|
||||
) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
clipboardManager.setText(AnnotatedString("nostr:${note.toNEvent()}"))
|
||||
scope.launch {
|
||||
clipboardManager.setText(AnnotatedString(note.toNostrUri()))
|
||||
showToast(R.string.copied_note_id_to_clipboard)
|
||||
onDismiss()
|
||||
}
|
||||
|
|
|
@ -197,9 +197,11 @@ fun NoteDropDownMenu(
|
|||
DropdownMenuItem(
|
||||
text = { Text(stringRes(R.string.copy_user_pubkey)) },
|
||||
onClick = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
clipboardManager.setText(AnnotatedString("nostr:${note.author?.pubkeyNpub()}"))
|
||||
onDismiss()
|
||||
note.author?.let {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
clipboardManager.setText(AnnotatedString("nostr:${it.pubkeyNpub()}"))
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -207,7 +209,7 @@ fun NoteDropDownMenu(
|
|||
text = { Text(stringRes(R.string.copy_note_id)) },
|
||||
onClick = {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
clipboardManager.setText(AnnotatedString("nostr:" + note.toNEvent()))
|
||||
clipboardManager.setText(AnnotatedString(note.toNostrUri()))
|
||||
onDismiss()
|
||||
}
|
||||
},
|
||||
|
|
|
@ -45,16 +45,22 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import com.vitorpamplona.amethyst.R
|
||||
import com.vitorpamplona.amethyst.model.FeatureSetType
|
||||
import com.vitorpamplona.amethyst.model.User
|
||||
import com.vitorpamplona.amethyst.ui.actions.CloseButton
|
||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||
import com.vitorpamplona.amethyst.ui.components.DisplayNIP05
|
||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||
import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.components.nip05VerificationAsAState
|
||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||
import com.vitorpamplona.amethyst.ui.stringRes
|
||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||
import com.vitorpamplona.quartz.events.UserMetadata
|
||||
|
@ -62,21 +68,20 @@ import com.vitorpamplona.quartz.events.UserMetadata
|
|||
@Preview
|
||||
@Composable
|
||||
fun ShowQRDialogPreview() {
|
||||
val user = User("460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c")
|
||||
|
||||
user.info =
|
||||
val accountViewModel = mockAccountViewModel()
|
||||
accountViewModel.userProfile().info =
|
||||
UserMetadata().apply {
|
||||
name = "My Name"
|
||||
picture = "Picture"
|
||||
nip05 = null
|
||||
banner = "http://banner.com/test"
|
||||
website = "http://mywebsite.com/test"
|
||||
about = "This is the about me"
|
||||
}
|
||||
|
||||
ShowQRDialog(
|
||||
user = user,
|
||||
loadProfilePicture = false,
|
||||
loadRobohash = false,
|
||||
user = accountViewModel.userProfile(),
|
||||
accountViewModel = accountViewModel,
|
||||
onScan = {},
|
||||
onClose = {},
|
||||
)
|
||||
|
@ -85,8 +90,7 @@ fun ShowQRDialogPreview() {
|
|||
@Composable
|
||||
fun ShowQRDialog(
|
||||
user: User,
|
||||
loadProfilePicture: Boolean,
|
||||
loadRobohash: Boolean,
|
||||
accountViewModel: AccountViewModel,
|
||||
onScan: (String) -> Unit,
|
||||
onClose: () -> Unit,
|
||||
) {
|
||||
|
@ -126,8 +130,8 @@ fun ShowQRDialog(
|
|||
.height(100.dp)
|
||||
.clip(shape = CircleShape)
|
||||
.border(3.dp, MaterialTheme.colorScheme.background, CircleShape),
|
||||
loadProfilePicture = loadProfilePicture,
|
||||
loadRobohash = loadRobohash,
|
||||
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
||||
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
|
@ -141,13 +145,34 @@ fun ShowQRDialog(
|
|||
fontSize = 18.sp,
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 4.dp),
|
||||
) {
|
||||
val nip05 = user.nip05()
|
||||
if (nip05 != null) {
|
||||
val nip05Verified =
|
||||
nip05VerificationAsAState(user.info!!, user.pubkeyHex, accountViewModel)
|
||||
|
||||
DisplayNIP05(nip05, nip05Verified, accountViewModel)
|
||||
} else {
|
||||
Text(
|
||||
text = user.pubkeyDisplayHex(),
|
||||
fontSize = 14.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = Size35dp),
|
||||
) {
|
||||
QrCodeDrawer("nostr:${user.pubkeyNpub()}")
|
||||
QrCodeDrawer(user.toNostrUri())
|
||||
}
|
||||
|
||||
Row(modifier = Modifier.padding(horizontal = 30.dp)) {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*/
|
||||
package com.vitorpamplona.amethyst.ui.screen.loggedIn
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
|
@ -103,6 +104,7 @@ import androidx.compose.ui.unit.Dp
|
|||
import androidx.compose.ui.unit.IntSize
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
|
@ -136,6 +138,7 @@ import com.vitorpamplona.amethyst.ui.note.DrawPlayName
|
|||
import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog
|
||||
import com.vitorpamplona.amethyst.ui.note.LightningAddressIcon
|
||||
import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote
|
||||
import com.vitorpamplona.amethyst.ui.note.externalLinkForUser
|
||||
import com.vitorpamplona.amethyst.ui.note.payViaIntent
|
||||
import com.vitorpamplona.amethyst.ui.qrcode.ShowQRDialog
|
||||
import com.vitorpamplona.amethyst.ui.screen.FeedState
|
||||
|
@ -996,9 +999,8 @@ private fun DrawAdditionalInfo(
|
|||
|
||||
if (dialogOpen) {
|
||||
ShowQRDialog(
|
||||
user,
|
||||
accountViewModel.settings.showProfilePictures.value,
|
||||
loadRobohash = accountViewModel.settings.featureSet != FeatureSetType.PERFORMANCE,
|
||||
user = user,
|
||||
accountViewModel = accountViewModel,
|
||||
onScan = {
|
||||
dialogOpen = false
|
||||
nav(it)
|
||||
|
@ -1870,6 +1872,32 @@ fun UserProfileDropDownMenu(
|
|||
},
|
||||
)
|
||||
|
||||
val actContext = LocalContext.current
|
||||
|
||||
DropdownMenuItem(
|
||||
text = { Text(stringRes(R.string.quick_action_share)) },
|
||||
onClick = {
|
||||
val sendIntent =
|
||||
Intent().apply {
|
||||
action = Intent.ACTION_SEND
|
||||
type = "text/plain"
|
||||
putExtra(
|
||||
Intent.EXTRA_TEXT,
|
||||
externalLinkForUser(user),
|
||||
)
|
||||
putExtra(
|
||||
Intent.EXTRA_TITLE,
|
||||
stringRes(actContext, R.string.quick_action_share_browser_link),
|
||||
)
|
||||
}
|
||||
|
||||
val shareIntent =
|
||||
Intent.createChooser(sendIntent, stringRes(actContext, R.string.quick_action_share))
|
||||
ContextCompat.startActivity(actContext, shareIntent, null)
|
||||
onDismiss()
|
||||
},
|
||||
)
|
||||
|
||||
if (accountViewModel.userProfile() != user) {
|
||||
HorizontalDivider(thickness = DividerThickness)
|
||||
if (accountViewModel.account.isHidden(user)) {
|
||||
|
|
|
@ -39,7 +39,9 @@ object Nip19Bech32 {
|
|||
ADDRESS,
|
||||
}
|
||||
|
||||
enum class TlvTypes(val id: Byte) {
|
||||
enum class TlvTypes(
|
||||
val id: Byte,
|
||||
) {
|
||||
SPECIAL(0),
|
||||
RELAY(1),
|
||||
AUTHOR(2),
|
||||
|
@ -53,33 +55,59 @@ object Nip19Bech32 {
|
|||
)
|
||||
|
||||
@Immutable
|
||||
data class ParseReturn(val entity: Entity, val additionalChars: String? = null)
|
||||
data class ParseReturn(
|
||||
val entity: Entity,
|
||||
val additionalChars: String? = null,
|
||||
)
|
||||
|
||||
interface Entity
|
||||
|
||||
@Immutable
|
||||
data class NSec(val hex: String) : Entity
|
||||
data class NSec(
|
||||
val hex: String,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NPub(val hex: String) : Entity
|
||||
data class NPub(
|
||||
val hex: String,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class Note(val hex: String) : Entity
|
||||
data class Note(
|
||||
val hex: String,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NProfile(val hex: String, val relay: List<String>) : Entity
|
||||
data class NProfile(
|
||||
val hex: String,
|
||||
val relay: List<String>,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NEvent(val hex: String, val relay: List<String>, val author: String?, val kind: Int?) : Entity
|
||||
data class NEvent(
|
||||
val hex: String,
|
||||
val relay: List<String>,
|
||||
val author: String?,
|
||||
val kind: Int?,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NAddress(val atag: String, val relay: List<String>, val author: String, val kind: Int) : Entity
|
||||
data class NAddress(
|
||||
val atag: String,
|
||||
val relay: List<String>,
|
||||
val author: String,
|
||||
val kind: Int,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NRelay(val relay: List<String>) : Entity
|
||||
data class NRelay(
|
||||
val relay: List<String>,
|
||||
) : Entity
|
||||
|
||||
@Immutable
|
||||
data class NEmbed(val event: Event) : Entity
|
||||
data class NEmbed(
|
||||
val event: Event,
|
||||
) : Entity
|
||||
|
||||
fun uriToRoute(uri: String?): ParseReturn? {
|
||||
if (uri == null) return null
|
||||
|
@ -108,8 +136,8 @@ object Nip19Bech32 {
|
|||
type: String,
|
||||
key: String?,
|
||||
additionalChars: String?,
|
||||
): ParseReturn? {
|
||||
return try {
|
||||
): ParseReturn? =
|
||||
try {
|
||||
val bytes = (type + key).bechToBytes()
|
||||
|
||||
when (type.lowercase()) {
|
||||
|
@ -129,7 +157,6 @@ object Nip19Bech32 {
|
|||
Log.w("NIP19 Parser", "Issue trying to Decode NIP19 $key: ${e.message}", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun nembed(bytes: ByteArray): NEmbed? {
|
||||
if (bytes.isEmpty()) return null
|
||||
|
@ -205,21 +232,30 @@ object Nip19Bech32 {
|
|||
author: String?,
|
||||
kind: Int?,
|
||||
relay: String?,
|
||||
): String {
|
||||
return TlvBuilder()
|
||||
): String =
|
||||
TlvBuilder()
|
||||
.apply {
|
||||
addHex(TlvTypes.SPECIAL, idHex)
|
||||
addStringIfNotNull(TlvTypes.RELAY, relay)
|
||||
addHexIfNotNull(TlvTypes.AUTHOR, author)
|
||||
addIntIfNotNull(TlvTypes.KIND, kind)
|
||||
}
|
||||
.build()
|
||||
}.build()
|
||||
.toNEvent()
|
||||
}
|
||||
|
||||
fun createNEmbed(event: Event): String {
|
||||
return gzip(event.toJson()).toNEmbed()
|
||||
}
|
||||
fun createNProfile(
|
||||
authorPubKeyHex: String,
|
||||
relay: List<String>,
|
||||
): String =
|
||||
TlvBuilder()
|
||||
.apply {
|
||||
addHex(TlvTypes.SPECIAL, authorPubKeyHex)
|
||||
relay.forEach {
|
||||
addStringIfNotNull(TlvTypes.RELAY, it)
|
||||
}
|
||||
}.build()
|
||||
.toNProfile()
|
||||
|
||||
fun createNEmbed(event: Event): String = gzip(event.toJson()).toNEmbed()
|
||||
|
||||
fun gzip(content: String): ByteArray {
|
||||
val bos = ByteArrayOutputStream()
|
||||
|
@ -239,23 +275,24 @@ fun ByteArray.toNote() = Bech32.encodeBytes(hrp = "note", this, Bech32.Encoding.
|
|||
|
||||
fun ByteArray.toNEvent() = Bech32.encodeBytes(hrp = "nevent", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun ByteArray.toNProfile() = Bech32.encodeBytes(hrp = "nprofile", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun ByteArray.toNAddress() = Bech32.encodeBytes(hrp = "naddr", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun ByteArray.toLnUrl() = Bech32.encodeBytes(hrp = "lnurl", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun ByteArray.toNEmbed() = Bech32.encodeBytes(hrp = "nembed", this, Bech32.Encoding.Bech32)
|
||||
|
||||
fun decodePublicKey(key: String): ByteArray {
|
||||
return when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
|
||||
fun decodePublicKey(key: String): ByteArray =
|
||||
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
|
||||
is Nip19Bech32.NSec -> KeyPair(privKey = key.bechToBytes()).pubKey
|
||||
is Nip19Bech32.NPub -> parsed.hex.hexToByteArray()
|
||||
is Nip19Bech32.NProfile -> parsed.hex.hexToByteArray()
|
||||
else -> Hex.decode(key) // crashes on purpose
|
||||
}
|
||||
}
|
||||
|
||||
fun decodePrivateKeyAsHexOrNull(key: String): HexKey? {
|
||||
return try {
|
||||
fun decodePrivateKeyAsHexOrNull(key: String): HexKey? =
|
||||
try {
|
||||
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
|
||||
is Nip19Bech32.NSec -> parsed.hex
|
||||
is Nip19Bech32.NPub -> null
|
||||
|
@ -271,10 +308,9 @@ fun decodePrivateKeyAsHexOrNull(key: String): HexKey? {
|
|||
if (e is CancellationException) throw e
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun decodePublicKeyAsHexOrNull(key: String): HexKey? {
|
||||
return try {
|
||||
fun decodePublicKeyAsHexOrNull(key: String): HexKey? =
|
||||
try {
|
||||
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
|
||||
is Nip19Bech32.NSec -> KeyPair(privKey = key.bechToBytes()).pubKey.toHexKey()
|
||||
is Nip19Bech32.NPub -> parsed.hex
|
||||
|
@ -290,10 +326,9 @@ fun decodePublicKeyAsHexOrNull(key: String): HexKey? {
|
|||
if (e is CancellationException) throw e
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun decodeEventIdAsHexOrNull(key: String): HexKey? {
|
||||
return try {
|
||||
fun decodeEventIdAsHexOrNull(key: String): HexKey? =
|
||||
try {
|
||||
when (val parsed = Nip19Bech32.uriToRoute(key)?.entity) {
|
||||
is Nip19Bech32.NSec -> null
|
||||
is Nip19Bech32.NPub -> null
|
||||
|
@ -309,7 +344,6 @@ fun decodeEventIdAsHexOrNull(key: String): HexKey? {
|
|||
if (e is CancellationException) throw e
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun TlvBuilder.addString(
|
||||
type: Nip19Bech32.TlvTypes,
|
||||
|
|
Ładowanie…
Reference in New Issue