Refactoring of Badge Box codes and Time classes
This commit is contained in:
@@ -19,7 +19,6 @@ import java.math.BigDecimal
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import java.util.Date
|
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]")
|
||||||
@@ -434,7 +433,7 @@ open class Note(val idHex: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun hasAnyReports(): Boolean {
|
fun hasAnyReports(): Boolean {
|
||||||
val dayAgo = Date().time / 1000 - 24 * 60 * 60
|
val dayAgo = TimeUtils.oneDayAgo()
|
||||||
return reports.isNotEmpty() ||
|
return reports.isNotEmpty() ||
|
||||||
(
|
(
|
||||||
author?.reports?.values?.any {
|
author?.reports?.values?.any {
|
||||||
@@ -471,8 +470,7 @@ open class Note(val idHex: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun hasBoostedInTheLast5Minutes(loggedIn: User): Boolean {
|
fun hasBoostedInTheLast5Minutes(loggedIn: User): Boolean {
|
||||||
val currentTime = Date().time / 1000
|
return boosts.firstOrNull { it.author == loggedIn && (it.createdAt() ?: 0) > TimeUtils.fiveMinutesAgo() } != null // 5 minute protection
|
||||||
return boosts.firstOrNull { it.author == loggedIn && (it.createdAt() ?: 0) > currentTime - (60 * 5) } != null // 5 minute protection
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun boostedBy(loggedIn: User): List<Note> {
|
fun boostedBy(loggedIn: User): List<Note> {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
|
object TimeUtils {
|
||||||
|
const val fiveMinutes = 60 * 5
|
||||||
|
const val oneHour = 60 * 60
|
||||||
|
const val oneDay = 24 * 60 * 60
|
||||||
|
|
||||||
|
fun now() = System.currentTimeMillis() / 1000
|
||||||
|
fun fiveMinutesAgo() = now() - fiveMinutes
|
||||||
|
fun oneHourAgo() = now() - oneHour
|
||||||
|
fun oneDayAgo() = now() - oneDay
|
||||||
|
fun eightHoursAgo() = now() - (oneHour * 8)
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.service
|
|||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.*
|
||||||
import com.vitorpamplona.amethyst.service.model.Event
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
@@ -12,7 +13,6 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.Date
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.Error
|
import kotlin.Error
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ abstract class NostrDataSource(val debugName: String) {
|
|||||||
|
|
||||||
if (type == Relay.Type.EOSE && channel != null) {
|
if (type == Relay.Type.EOSE && channel != null) {
|
||||||
// updates a per subscripton since date
|
// updates a per subscripton since date
|
||||||
subscriptions[channel]?.updateEOSE(Date().time / 1000, relay.url)
|
subscriptions[channel]?.updateEOSE(TimeUtils.now(), relay.url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class AudioTrackEvent(
|
class AudioTrackEvent(
|
||||||
@@ -42,7 +42,7 @@ class AudioTrackEvent(
|
|||||||
cover: String? = null,
|
cover: String? = null,
|
||||||
subject: String? = null,
|
subject: String? = null,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): AudioTrackEvent {
|
): AudioTrackEvent {
|
||||||
val tags = listOfNotNull(
|
val tags = listOfNotNull(
|
||||||
listOf(MEDIA, media),
|
listOf(MEDIA, media),
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class BookmarkListEvent(
|
class BookmarkListEvent(
|
||||||
@@ -30,7 +30,7 @@ class BookmarkListEvent(
|
|||||||
privAddresses: List<ATag>? = null,
|
privAddresses: List<ATag>? = null,
|
||||||
|
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): BookmarkListEvent {
|
): BookmarkListEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
val content = createPrivateTags(privEvents, privUsers, privAddresses, privateKey, pubKey)
|
val content = createPrivateTags(privEvents, privUsers, privAddresses, privateKey, pubKey)
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class ChannelCreateEvent(
|
class ChannelCreateEvent(
|
||||||
@@ -26,7 +26,7 @@ class ChannelCreateEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 40
|
const val kind = 40
|
||||||
|
|
||||||
fun create(channelInfo: ChannelData?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelCreateEvent {
|
fun create(channelInfo: ChannelData?, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ChannelCreateEvent {
|
||||||
val content = try {
|
val content = try {
|
||||||
if (channelInfo != null) {
|
if (channelInfo != null) {
|
||||||
gson.toJson(channelInfo)
|
gson.toJson(channelInfo)
|
||||||
|
|||||||
+2
-2
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class ChannelHideMessageEvent(
|
class ChannelHideMessageEvent(
|
||||||
@@ -26,7 +26,7 @@ class ChannelHideMessageEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 43
|
const val kind = 43
|
||||||
|
|
||||||
fun create(reason: String, messagesToHide: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelHideMessageEvent {
|
fun create(reason: String, messagesToHide: List<String>?, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ChannelHideMessageEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags =
|
val tags =
|
||||||
messagesToHide?.map {
|
messagesToHide?.map {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class ChannelMessageEvent(
|
class ChannelMessageEvent(
|
||||||
@@ -34,7 +34,7 @@ class ChannelMessageEvent(
|
|||||||
mentions: List<String>? = null,
|
mentions: List<String>? = null,
|
||||||
zapReceiver: String?,
|
zapReceiver: String?,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000,
|
createdAt: Long = TimeUtils.now(),
|
||||||
markAsSensitive: Boolean,
|
markAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long?
|
zapRaiserAmount: Long?
|
||||||
): ChannelMessageEvent {
|
): ChannelMessageEvent {
|
||||||
|
|||||||
@@ -3,9 +3,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class ChannelMetadataEvent(
|
class ChannelMetadataEvent(
|
||||||
@@ -29,7 +29,7 @@ class ChannelMetadataEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 41
|
const val kind = 41
|
||||||
|
|
||||||
fun create(newChannelInfo: ChannelCreateEvent.ChannelData?, originalChannelIdHex: String, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMetadataEvent {
|
fun create(newChannelInfo: ChannelCreateEvent.ChannelData?, originalChannelIdHex: String, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ChannelMetadataEvent {
|
||||||
val content =
|
val content =
|
||||||
if (newChannelInfo != null) {
|
if (newChannelInfo != null) {
|
||||||
gson.toJson(newChannelInfo)
|
gson.toJson(newChannelInfo)
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class ChannelMuteUserEvent(
|
class ChannelMuteUserEvent(
|
||||||
@@ -26,7 +26,7 @@ class ChannelMuteUserEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 44
|
const val kind = 44
|
||||||
|
|
||||||
fun create(reason: String, usersToMute: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ChannelMuteUserEvent {
|
fun create(reason: String, usersToMute: List<String>?, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ChannelMuteUserEvent {
|
||||||
val content = reason
|
val content = reason
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags =
|
val tags =
|
||||||
|
|||||||
+2
-2
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class CommunityDefinitionEvent(
|
class CommunityDefinitionEvent(
|
||||||
@@ -30,7 +30,7 @@ class CommunityDefinitionEvent(
|
|||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): CommunityDefinitionEvent {
|
): CommunityDefinitionEvent {
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
|
|||||||
+2
-2
@@ -3,10 +3,10 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class CommunityPostApprovalEvent(
|
class CommunityPostApprovalEvent(
|
||||||
@@ -45,7 +45,7 @@ class CommunityPostApprovalEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 4550
|
const val kind = 4550
|
||||||
|
|
||||||
fun create(approvedPost: Event, community: CommunityDefinitionEvent, privateKey: ByteArray, createdAt: Long = Date().time / 1000): GenericRepostEvent {
|
fun create(approvedPost: Event, community: CommunityDefinitionEvent, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): GenericRepostEvent {
|
||||||
val content = approvedPost.toJson()
|
val content = approvedPost.toJson()
|
||||||
|
|
||||||
val communities = listOf("a", community.address().toTag())
|
val communities = listOf("a", community.address().toTag())
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import androidx.compose.runtime.Immutable
|
|||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.decodePublicKey
|
import com.vitorpamplona.amethyst.model.decodePublicKey
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class Contact(val pubKeyHex: String, val relayUri: String?)
|
data class Contact(val pubKeyHex: String, val relayUri: String?)
|
||||||
@@ -73,7 +73,7 @@ class ContactListEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 3
|
const val kind = 3
|
||||||
|
|
||||||
fun create(follows: List<Contact>, followTags: List<String>, relayUse: Map<String, ReadWrite>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ContactListEvent {
|
fun create(follows: List<Contact>, followTags: List<String>, relayUse: Map<String, ReadWrite>?, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ContactListEvent {
|
||||||
val content = if (relayUse != null) {
|
val content = if (relayUse != null) {
|
||||||
gson.toJson(relayUse)
|
gson.toJson(relayUse)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class DeletionEvent(
|
class DeletionEvent(
|
||||||
@@ -20,7 +20,7 @@ class DeletionEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 5
|
const val kind = 5
|
||||||
|
|
||||||
fun create(deleteEvents: List<String>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): DeletionEvent {
|
fun create(deleteEvents: List<String>, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): DeletionEvent {
|
||||||
val content = ""
|
val content = ""
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = deleteEvents.map { listOf("e", it) }
|
val tags = deleteEvents.map { listOf("e", it) }
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.compose.runtime.Immutable
|
|||||||
import com.google.gson.*
|
import com.google.gson.*
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import fr.acinq.secp256k1.Hex
|
import fr.acinq.secp256k1.Hex
|
||||||
import fr.acinq.secp256k1.Secp256k1
|
import fr.acinq.secp256k1.Secp256k1
|
||||||
@@ -314,7 +315,7 @@ open class Event(
|
|||||||
return MessageDigest.getInstance("SHA-256").digest(rawEventJson.toByteArray())
|
return MessageDigest.getInstance("SHA-256").digest(rawEventJson.toByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(privateKey: ByteArray, kind: Int, tags: List<List<String>> = emptyList(), content: String = "", createdAt: Long = Date().time / 1000): Event {
|
fun create(privateKey: ByteArray, kind: Int, tags: List<List<String>> = emptyList(), content: String = "", createdAt: Long = TimeUtils.now()): Event {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val id = Companion.generateId(pubKey, createdAt, kind, tags, content)
|
val id = Companion.generateId(pubKey, createdAt, kind, tags, content)
|
||||||
val sig = Utils.sign(id, privateKey).toHexKey()
|
val sig = Utils.sign(id, privateKey).toHexKey()
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class FileHeaderEvent(
|
class FileHeaderEvent(
|
||||||
@@ -52,7 +52,7 @@ class FileHeaderEvent(
|
|||||||
encryptionKey: AESGCM? = null,
|
encryptionKey: AESGCM? = null,
|
||||||
sensitiveContent: Boolean? = null,
|
sensitiveContent: Boolean? = null,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): FileHeaderEvent {
|
): FileHeaderEvent {
|
||||||
val tags = listOfNotNull(
|
val tags = listOfNotNull(
|
||||||
listOf(URL, url),
|
listOf(URL, url),
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Base64
|
import java.util.Base64
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class FileStorageEvent(
|
class FileStorageEvent(
|
||||||
@@ -44,7 +44,7 @@ class FileStorageEvent(
|
|||||||
mimeType: String,
|
mimeType: String,
|
||||||
data: ByteArray,
|
data: ByteArray,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): FileStorageEvent {
|
): FileStorageEvent {
|
||||||
val tags = listOfNotNull(
|
val tags = listOfNotNull(
|
||||||
listOf(TYPE, mimeType)
|
listOf(TYPE, mimeType)
|
||||||
|
|||||||
+2
-2
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class FileStorageHeaderEvent(
|
class FileStorageHeaderEvent(
|
||||||
@@ -52,7 +52,7 @@ class FileStorageHeaderEvent(
|
|||||||
encryptionKey: AESGCM? = null,
|
encryptionKey: AESGCM? = null,
|
||||||
sensitiveContent: Boolean? = null,
|
sensitiveContent: Boolean? = null,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): FileStorageHeaderEvent {
|
): FileStorageHeaderEvent {
|
||||||
val tags = listOfNotNull(
|
val tags = listOfNotNull(
|
||||||
listOf("e", storageEvent.id),
|
listOf("e", storageEvent.id),
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class GenericRepostEvent(
|
class GenericRepostEvent(
|
||||||
@@ -29,7 +29,7 @@ class GenericRepostEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 16
|
const val kind = 16
|
||||||
|
|
||||||
fun create(boostedPost: EventInterface, privateKey: ByteArray, createdAt: Long = Date().time / 1000): GenericRepostEvent {
|
fun create(boostedPost: EventInterface, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): GenericRepostEvent {
|
||||||
val content = boostedPost.toJson()
|
val content = boostedPost.toJson()
|
||||||
|
|
||||||
val replyToPost = listOf("e", boostedPost.id())
|
val replyToPost = listOf("e", boostedPost.id())
|
||||||
|
|||||||
+2
-2
@@ -2,10 +2,10 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class HTTPAuthorizationEvent(
|
class HTTPAuthorizationEvent(
|
||||||
@@ -25,7 +25,7 @@ class HTTPAuthorizationEvent(
|
|||||||
method: String,
|
method: String,
|
||||||
body: String? = null,
|
body: String? = null,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): HTTPAuthorizationEvent {
|
): HTTPAuthorizationEvent {
|
||||||
val sha256 = MessageDigest.getInstance("SHA-256")
|
val sha256 = MessageDigest.getInstance("SHA-256")
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class HighlightEvent(
|
class HighlightEvent(
|
||||||
@@ -26,7 +26,7 @@ class HighlightEvent(
|
|||||||
fun create(
|
fun create(
|
||||||
msg: String,
|
msg: String,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): PollNoteEvent {
|
): PollNoteEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
|
|||||||
+2
-2
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class LiveActivitiesChatMessageEvent(
|
class LiveActivitiesChatMessageEvent(
|
||||||
@@ -49,7 +49,7 @@ class LiveActivitiesChatMessageEvent(
|
|||||||
mentions: List<String>? = null,
|
mentions: List<String>? = null,
|
||||||
zapReceiver: String?,
|
zapReceiver: String?,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000,
|
createdAt: Long = TimeUtils.now(),
|
||||||
markAsSensitive: Boolean,
|
markAsSensitive: Boolean,
|
||||||
zapRaiserAmount: Long?
|
zapRaiserAmount: Long?
|
||||||
): LiveActivitiesChatMessageEvent {
|
): LiveActivitiesChatMessageEvent {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class LiveActivitiesEvent(
|
class LiveActivitiesEvent(
|
||||||
@@ -32,7 +32,7 @@ class LiveActivitiesEvent(
|
|||||||
fun participants() = tags.filter { it.size > 1 && it[0] == "p" }.map { Participant(it[1], it.getOrNull(3)) }
|
fun participants() = tags.filter { it.size > 1 && it[0] == "p" }.map { Participant(it[1], it.getOrNull(3)) }
|
||||||
|
|
||||||
fun checkStatus(eventStatus: String?): String? {
|
fun checkStatus(eventStatus: String?): String? {
|
||||||
return if (eventStatus == STATUS_LIVE && createdAt < Date().time / 1000 - (60 * 60 * 8)) { // 2 hours {
|
return if (eventStatus == STATUS_LIVE && createdAt < TimeUtils.eightHoursAgo()) {
|
||||||
STATUS_ENDED
|
STATUS_ENDED
|
||||||
} else {
|
} else {
|
||||||
eventStatus
|
eventStatus
|
||||||
@@ -48,7 +48,7 @@ class LiveActivitiesEvent(
|
|||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): LiveActivitiesEvent {
|
): LiveActivitiesEvent {
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
|
|||||||
+2
-2
@@ -7,11 +7,11 @@ import com.google.gson.JsonDeserializer
|
|||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import com.google.gson.JsonParseException
|
import com.google.gson.JsonParseException
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.hexToByteArray
|
import com.vitorpamplona.amethyst.model.hexToByteArray
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.lang.reflect.Type
|
import java.lang.reflect.Type
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class LnZapPaymentRequestEvent(
|
class LnZapPaymentRequestEvent(
|
||||||
@@ -57,7 +57,7 @@ class LnZapPaymentRequestEvent(
|
|||||||
lnInvoice: String,
|
lnInvoice: String,
|
||||||
walletServicePubkey: String,
|
walletServicePubkey: String,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): LnZapPaymentRequestEvent {
|
): LnZapPaymentRequestEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
val serializedRequest = gson.toJson(PayInvoiceMethod.create(lnInvoice))
|
val serializedRequest = gson.toJson(PayInvoiceMethod.create(lnInvoice))
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class LnZapRequestEvent(
|
|||||||
pollOption: Int?,
|
pollOption: Int?,
|
||||||
message: String,
|
message: String,
|
||||||
zapType: LnZapEvent.ZapType,
|
zapType: LnZapEvent.ZapType,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): LnZapRequestEvent {
|
): LnZapRequestEvent {
|
||||||
var content = message
|
var content = message
|
||||||
var privkey = privateKey
|
var privkey = privateKey
|
||||||
@@ -104,7 +104,7 @@ class LnZapRequestEvent(
|
|||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
message: String,
|
message: String,
|
||||||
zapType: LnZapEvent.ZapType,
|
zapType: LnZapEvent.ZapType,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): LnZapRequestEvent {
|
): LnZapRequestEvent {
|
||||||
var content = message
|
var content = message
|
||||||
var privkey = privateKey
|
var privkey = privateKey
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class LongTextNoteEvent(
|
class LongTextNoteEvent(
|
||||||
@@ -32,7 +32,7 @@ class LongTextNoteEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 30023
|
const val kind = 30023
|
||||||
|
|
||||||
fun create(msg: String, replyTos: List<String>?, mentions: List<String>?, privateKey: ByteArray, createdAt: Long = Date().time / 1000): LongTextNoteEvent {
|
fun create(msg: String, replyTos: List<String>?, mentions: List<String>?, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): LongTextNoteEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
replyTos?.forEach {
|
replyTos?.forEach {
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.UserMetadata
|
import com.vitorpamplona.amethyst.model.UserMetadata
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
abstract class IdentityClaim(
|
abstract class IdentityClaim(
|
||||||
@@ -171,7 +171,7 @@ class MetadataEvent(
|
|||||||
.readerFor(UserMetadata::class.java)
|
.readerFor(UserMetadata::class.java)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(contactMetaData: String, identities: List<IdentityClaim>, privateKey: ByteArray, createdAt: Long = Date().time / 1000): MetadataEvent {
|
fun create(contactMetaData: String, identities: List<IdentityClaim>, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): MetadataEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import android.util.Log
|
|||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.hexToByteArray
|
import com.vitorpamplona.amethyst.model.hexToByteArray
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class MuteListEvent(
|
class MuteListEvent(
|
||||||
@@ -71,7 +71,7 @@ class MuteListEvent(
|
|||||||
privAddresses: List<ATag>? = null,
|
privAddresses: List<ATag>? = null,
|
||||||
|
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): MuteListEvent {
|
): MuteListEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class PeopleListEvent(
|
class PeopleListEvent(
|
||||||
@@ -30,7 +30,7 @@ class PeopleListEvent(
|
|||||||
privAddresses: List<ATag>? = null,
|
privAddresses: List<ATag>? = null,
|
||||||
|
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): PeopleListEvent {
|
): PeopleListEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey)
|
val pubKey = Utils.pubkeyCreate(privateKey)
|
||||||
val content = createPrivateTags(privEvents, privUsers, privAddresses, privateKey, pubKey)
|
val content = createPrivateTags(privEvents, privUsers, privAddresses, privateKey, pubKey)
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class PinListEvent(
|
class PinListEvent(
|
||||||
@@ -27,7 +27,7 @@ class PinListEvent(
|
|||||||
fun create(
|
fun create(
|
||||||
pins: List<String>,
|
pins: List<String>,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): PinListEvent {
|
): PinListEvent {
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
pins.forEach {
|
pins.forEach {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
const val POLL_OPTION = "poll_option"
|
const val POLL_OPTION = "poll_option"
|
||||||
const val VALUE_MAXIMUM = "value_maximum"
|
const val VALUE_MAXIMUM = "value_maximum"
|
||||||
@@ -45,7 +45,7 @@ class PollNoteEvent(
|
|||||||
mentions: List<String>?,
|
mentions: List<String>?,
|
||||||
addresses: List<ATag>?,
|
addresses: List<ATag>?,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000,
|
createdAt: Long = TimeUtils.now(),
|
||||||
pollOptions: Map<Int, String>,
|
pollOptions: Map<Int, String>,
|
||||||
valueMaximum: Int?,
|
valueMaximum: Int?,
|
||||||
valueMinimum: Int?,
|
valueMinimum: Int?,
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import com.vitorpamplona.amethyst.service.HexValidator
|
import com.vitorpamplona.amethyst.service.HexValidator
|
||||||
import fr.acinq.secp256k1.Hex
|
import fr.acinq.secp256k1.Hex
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class PrivateDmEvent(
|
class PrivateDmEvent(
|
||||||
@@ -83,7 +83,7 @@ class PrivateDmEvent(
|
|||||||
mentions: List<String>? = null,
|
mentions: List<String>? = null,
|
||||||
zapReceiver: String?,
|
zapReceiver: String?,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000,
|
createdAt: Long = TimeUtils.now(),
|
||||||
publishedRecipientPubKey: ByteArray? = null,
|
publishedRecipientPubKey: ByteArray? = null,
|
||||||
advertiseNip18: Boolean = true,
|
advertiseNip18: Boolean = true,
|
||||||
markAsSensitive: Boolean,
|
markAsSensitive: Boolean,
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class ReactionEvent(
|
class ReactionEvent(
|
||||||
@@ -22,15 +22,15 @@ class ReactionEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 7
|
const val kind = 7
|
||||||
|
|
||||||
fun createWarning(originalNote: EventInterface, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
fun createWarning(originalNote: EventInterface, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ReactionEvent {
|
||||||
return create("\u26A0\uFE0F", originalNote, privateKey, createdAt)
|
return create("\u26A0\uFE0F", originalNote, privateKey, createdAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createLike(originalNote: EventInterface, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
fun createLike(originalNote: EventInterface, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ReactionEvent {
|
||||||
return create("+", originalNote, privateKey, createdAt)
|
return create("+", originalNote, privateKey, createdAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(content: String, originalNote: EventInterface, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReactionEvent {
|
fun create(content: String, originalNote: EventInterface, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ReactionEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
|
|
||||||
var tags = listOf(listOf("e", originalNote.id()), listOf("p", originalNote.pubKey()))
|
var tags = listOf(listOf("e", originalNote.id()), listOf("p", originalNote.pubKey()))
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class RecommendRelayEvent(
|
class RecommendRelayEvent(
|
||||||
@@ -27,7 +27,7 @@ class RecommendRelayEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 2
|
const val kind = 2
|
||||||
|
|
||||||
fun create(relay: URI, privateKey: ByteArray, createdAt: Long = Date().time / 1000): RecommendRelayEvent {
|
fun create(relay: URI, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): RecommendRelayEvent {
|
||||||
val content = relay.toString()
|
val content = relay.toString()
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = listOf<List<String>>()
|
val tags = listOf<List<String>>()
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class RelayAuthEvent(
|
class RelayAuthEvent(
|
||||||
@@ -21,7 +21,7 @@ class RelayAuthEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 22242
|
const val kind = 22242
|
||||||
|
|
||||||
fun create(relay: String, challenge: String, privateKey: ByteArray, createdAt: Long = Date().time / 1000): RelayAuthEvent {
|
fun create(relay: String, challenge: String, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): RelayAuthEvent {
|
||||||
val content = ""
|
val content = ""
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = listOf(
|
val tags = listOf(
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class RelaySetEvent(
|
class RelaySetEvent(
|
||||||
@@ -28,7 +28,7 @@ class RelaySetEvent(
|
|||||||
fun create(
|
fun create(
|
||||||
relays: List<String>,
|
relays: List<String>,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): RelaySetEvent {
|
): RelaySetEvent {
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
relays.forEach {
|
relays.forEach {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ReportedKey(val key: String, val reportType: ReportEvent.ReportType)
|
data class ReportedKey(val key: String, val reportType: ReportEvent.ReportType)
|
||||||
@@ -58,7 +58,7 @@ class ReportEvent(
|
|||||||
type: ReportType,
|
type: ReportType,
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
content: String = "",
|
content: String = "",
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): ReportEvent {
|
): ReportEvent {
|
||||||
val reportPostTag = listOf("e", reportedPost.id(), type.name.lowercase())
|
val reportPostTag = listOf("e", reportedPost.id(), type.name.lowercase())
|
||||||
val reportAuthorTag = listOf("p", reportedPost.pubKey(), type.name.lowercase())
|
val reportAuthorTag = listOf("p", reportedPost.pubKey(), type.name.lowercase())
|
||||||
@@ -75,7 +75,7 @@ class ReportEvent(
|
|||||||
return ReportEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
return ReportEvent(id.toHexKey(), pubKey, createdAt, tags, content, sig.toHexKey())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(reportedUser: String, type: ReportType, privateKey: ByteArray, createdAt: Long = Date().time / 1000): ReportEvent {
|
fun create(reportedUser: String, type: ReportType, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): ReportEvent {
|
||||||
val content = ""
|
val content = ""
|
||||||
|
|
||||||
val reportAuthorTag = listOf("p", reportedUser, type.name.lowercase())
|
val reportAuthorTag = listOf("p", reportedUser, type.name.lowercase())
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ package com.vitorpamplona.amethyst.service.model
|
|||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import com.vitorpamplona.amethyst.service.relays.Client
|
import com.vitorpamplona.amethyst.service.relays.Client
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class RepostEvent(
|
class RepostEvent(
|
||||||
@@ -29,7 +29,7 @@ class RepostEvent(
|
|||||||
companion object {
|
companion object {
|
||||||
const val kind = 6
|
const val kind = 6
|
||||||
|
|
||||||
fun create(boostedPost: EventInterface, privateKey: ByteArray, createdAt: Long = Date().time / 1000): RepostEvent {
|
fun create(boostedPost: EventInterface, privateKey: ByteArray, createdAt: Long = TimeUtils.now()): RepostEvent {
|
||||||
val content = boostedPost.toJson()
|
val content = boostedPost.toJson()
|
||||||
|
|
||||||
val replyToPost = listOf("e", boostedPost.id())
|
val replyToPost = listOf("e", boostedPost.id())
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import androidx.compose.runtime.Immutable
|
|||||||
import com.linkedin.urls.detection.UrlDetector
|
import com.linkedin.urls.detection.UrlDetector
|
||||||
import com.linkedin.urls.detection.UrlDetectorOptions
|
import com.linkedin.urls.detection.UrlDetectorOptions
|
||||||
import com.vitorpamplona.amethyst.model.HexKey
|
import com.vitorpamplona.amethyst.model.HexKey
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.toHexKey
|
import com.vitorpamplona.amethyst.model.toHexKey
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.findHashtags
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.findHashtags
|
||||||
import nostr.postr.Utils
|
import nostr.postr.Utils
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class TextNoteEvent(
|
class TextNoteEvent(
|
||||||
@@ -40,7 +40,7 @@ class TextNoteEvent(
|
|||||||
directMentions: Set<HexKey>,
|
directMentions: Set<HexKey>,
|
||||||
|
|
||||||
privateKey: ByteArray,
|
privateKey: ByteArray,
|
||||||
createdAt: Long = Date().time / 1000
|
createdAt: Long = TimeUtils.now()
|
||||||
): TextNoteEvent {
|
): TextNoteEvent {
|
||||||
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
val pubKey = Utils.pubkeyCreate(privateKey).toHexKey()
|
||||||
val tags = mutableListOf<List<String>>()
|
val tags = mutableListOf<List<String>>()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.service.relays
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.gson.JsonElement
|
import com.google.gson.JsonElement
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.service.model.Event
|
import com.vitorpamplona.amethyst.service.model.Event
|
||||||
import com.vitorpamplona.amethyst.service.model.EventInterface
|
import com.vitorpamplona.amethyst.service.model.EventInterface
|
||||||
@@ -14,7 +15,6 @@ import okhttp3.WebSocket
|
|||||||
import okhttp3.WebSocketListener
|
import okhttp3.WebSocketListener
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
enum class FeedType {
|
enum class FeedType {
|
||||||
FOLLOWS, PUBLIC_CHATS, PRIVATE_DMS, GLOBAL, SEARCH, WALLET_CONNECT
|
FOLLOWS, PUBLIC_CHATS, PRIVATE_DMS, GLOBAL, SEARCH, WALLET_CONNECT
|
||||||
@@ -179,7 +179,7 @@ class Relay(
|
|||||||
socket = null
|
socket = null
|
||||||
isReady = false
|
isReady = false
|
||||||
afterEOSE = false
|
afterEOSE = false
|
||||||
closingTime = Date().time / 1000
|
closingTime = TimeUtils.now()
|
||||||
listeners.forEach { it.onRelayStateChange(this@Relay, Type.DISCONNECT, null) }
|
listeners.forEach { it.onRelayStateChange(this@Relay, Type.DISCONNECT, null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ class Relay(
|
|||||||
socket = null
|
socket = null
|
||||||
isReady = false
|
isReady = false
|
||||||
afterEOSE = false
|
afterEOSE = false
|
||||||
closingTime = Date().time / 1000
|
closingTime = TimeUtils.now()
|
||||||
|
|
||||||
Log.w("Relay", "Relay onFailure $url, ${response?.message} $response")
|
Log.w("Relay", "Relay onFailure $url, ${response?.message} $response")
|
||||||
t.printStackTrace()
|
t.printStackTrace()
|
||||||
@@ -208,7 +208,7 @@ class Relay(
|
|||||||
errorCounter++
|
errorCounter++
|
||||||
isReady = false
|
isReady = false
|
||||||
afterEOSE = false
|
afterEOSE = false
|
||||||
closingTime = Date().time / 1000
|
closingTime = TimeUtils.now()
|
||||||
Log.e("Relay", "Relay Invalid $url")
|
Log.e("Relay", "Relay Invalid $url")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
@@ -216,7 +216,7 @@ class Relay(
|
|||||||
|
|
||||||
fun disconnect() {
|
fun disconnect() {
|
||||||
// httpClient.dispatcher.executorService.shutdown()
|
// httpClient.dispatcher.executorService.shutdown()
|
||||||
closingTime = Date().time / 1000
|
closingTime = TimeUtils.now()
|
||||||
socket?.close(1000, "Normal close")
|
socket?.close(1000, "Normal close")
|
||||||
socket = null
|
socket = null
|
||||||
isReady = false
|
isReady = false
|
||||||
@@ -241,7 +241,7 @@ class Relay(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// waits 60 seconds to reconnect after disconnected.
|
// waits 60 seconds to reconnect after disconnected.
|
||||||
if (Date().time / 1000 > closingTime + 60) {
|
if (TimeUtils.now() > closingTime + 60) {
|
||||||
// sends all filters after connection is successful.
|
// sends all filters after connection is successful.
|
||||||
requestAndWatch()
|
requestAndWatch()
|
||||||
}
|
}
|
||||||
@@ -254,7 +254,7 @@ class Relay(
|
|||||||
|
|
||||||
if (socket == null) {
|
if (socket == null) {
|
||||||
// waits 60 seconds to reconnect after disconnected.
|
// waits 60 seconds to reconnect after disconnected.
|
||||||
if (Date().time / 1000 > closingTime + 60) {
|
if (TimeUtils.now() > closingTime + 60) {
|
||||||
// println("sendfilter Only if Disconnected ${url} ")
|
// println("sendfilter Only if Disconnected ${url} ")
|
||||||
requestAndWatch()
|
requestAndWatch()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
|||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
|
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.*
|
||||||
|
|
||||||
open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
|
open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
|
||||||
@@ -25,7 +26,7 @@ open class DiscoverChatFeedFilter(val account: Account) : AdditiveFeedFilter<Not
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||||
val now = System.currentTimeMillis() / 1000
|
val now = TimeUtils.now()
|
||||||
val isGlobal = account.defaultDiscoveryFollowList == GLOBAL_FOLLOWS
|
val isGlobal = account.defaultDiscoveryFollowList == GLOBAL_FOLLOWS
|
||||||
|
|
||||||
val followingKeySet = account.selectedUsersFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
val followingKeySet = account.selectedUsersFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
|||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
|
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.*
|
||||||
|
|
||||||
open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
|
open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
|
||||||
@@ -25,7 +26,7 @@ open class DiscoverCommunityFeedFilter(val account: Account) : AdditiveFeedFilte
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||||
val now = System.currentTimeMillis() / 1000
|
val now = TimeUtils.now()
|
||||||
val isGlobal = account.defaultDiscoveryFollowList == GLOBAL_FOLLOWS
|
val isGlobal = account.defaultDiscoveryFollowList == GLOBAL_FOLLOWS
|
||||||
|
|
||||||
val followingKeySet = account.selectedUsersFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
val followingKeySet = account.selectedUsersFollowList(account.defaultDiscoveryFollowList) ?: emptySet()
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
|||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
|
import com.vitorpamplona.amethyst.model.ParticipantListBuilder
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.*
|
||||||
import com.vitorpamplona.amethyst.service.model.LiveActivitiesEvent.Companion.STATUS_ENDED
|
import com.vitorpamplona.amethyst.service.model.LiveActivitiesEvent.Companion.STATUS_ENDED
|
||||||
import com.vitorpamplona.amethyst.service.model.LiveActivitiesEvent.Companion.STATUS_LIVE
|
import com.vitorpamplona.amethyst.service.model.LiveActivitiesEvent.Companion.STATUS_LIVE
|
||||||
@@ -30,7 +31,7 @@ open class DiscoverLiveFeedFilter(val account: Account) : AdditiveFeedFilter<Not
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
protected open fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||||
val now = System.currentTimeMillis() / 1000
|
val now = TimeUtils.now()
|
||||||
val isGlobal = account.defaultDiscoveryFollowList == GLOBAL_FOLLOWS
|
val isGlobal = account.defaultDiscoveryFollowList == GLOBAL_FOLLOWS
|
||||||
|
|
||||||
val followingKeySet =
|
val followingKeySet =
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMessageEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.LiveActivitiesChatMessageEvent
|
import com.vitorpamplona.amethyst.service.model.LiveActivitiesChatMessageEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||||
@@ -29,7 +30,7 @@ class HomeConversationsFeedFilter(val account: Account) : AdditiveFeedFilter<Not
|
|||||||
val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet()
|
val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet()
|
val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
|
|
||||||
val now = System.currentTimeMillis() / 1000
|
val now = TimeUtils.now()
|
||||||
|
|
||||||
return collection
|
return collection
|
||||||
.asSequence()
|
.asSequence()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
|
import com.vitorpamplona.amethyst.service.model.AudioTrackEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.GenericRepostEvent
|
import com.vitorpamplona.amethyst.service.model.GenericRepostEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
import com.vitorpamplona.amethyst.service.model.HighlightEvent
|
||||||
@@ -11,7 +12,6 @@ import com.vitorpamplona.amethyst.service.model.LongTextNoteEvent
|
|||||||
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
import com.vitorpamplona.amethyst.service.model.PollNoteEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
import com.vitorpamplona.amethyst.service.model.TextNoteEvent
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
|
class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
|
||||||
override fun feedKey(): String {
|
override fun feedKey(): String {
|
||||||
@@ -35,7 +35,7 @@ class HomeNewThreadFeedFilter(val account: Account) : AdditiveFeedFilter<Note>()
|
|||||||
val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet()
|
val followingKeySet = account.selectedUsersFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet()
|
val followingTagSet = account.selectedTagsFollowList(account.defaultHomeFollowList) ?: emptySet()
|
||||||
|
|
||||||
val oneMinuteInTheFuture = Date().time / 1000 + (1 * 60) // one minute in the future.
|
val oneMinuteInTheFuture = TimeUtils.now() + (1 * 60) // one minute in the future.
|
||||||
val oneHr = 60 * 60
|
val oneHr = 60 * 60
|
||||||
|
|
||||||
return collection
|
return collection
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.vitorpamplona.amethyst.model.Account
|
|||||||
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.*
|
||||||
|
|
||||||
class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
|
class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
|
||||||
@@ -22,7 +23,7 @@ class VideoFeedFilter(val account: Account) : AdditiveFeedFilter<Note>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
private fun innerApplyFilter(collection: Collection<Note>): Set<Note> {
|
||||||
val now = System.currentTimeMillis() / 1000
|
val now = TimeUtils.now()
|
||||||
val isGlobal = account.defaultStoriesFollowList == GLOBAL_FOLLOWS
|
val isGlobal = account.defaultStoriesFollowList == GLOBAL_FOLLOWS
|
||||||
|
|
||||||
val followingKeySet = account.selectedUsersFollowList(account.defaultStoriesFollowList) ?: emptySet()
|
val followingKeySet = account.selectedUsersFollowList(account.defaultStoriesFollowList) ?: emptySet()
|
||||||
|
|||||||
@@ -5,29 +5,19 @@ import androidx.compose.foundation.ExperimentalFoundationApi
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.IconButton
|
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.ChevronRight
|
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.MutableState
|
import androidx.compose.runtime.MutableState
|
||||||
@@ -36,7 +26,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -48,26 +37,21 @@ import androidx.compose.ui.layout.onSizeChanged
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.RelayInformation
|
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelMetadataEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
import com.vitorpamplona.amethyst.ui.actions.ImmutableListOfLists
|
||||||
import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.loadRelayInfo
|
|
||||||
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
import com.vitorpamplona.amethyst.ui.actions.toImmutableListOfLists
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||||
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -76,13 +60,9 @@ import com.vitorpamplona.amethyst.ui.theme.ChatBubbleShapeMe
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.ChatBubbleShapeThem
|
import com.vitorpamplona.amethyst.ui.theme.ChatBubbleShapeThem
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightChat
|
import com.vitorpamplona.amethyst.ui.theme.ReactionRowHeightChat
|
||||||
import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size13dp
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size15dp
|
import com.vitorpamplona.amethyst.ui.theme.Size15dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
import com.vitorpamplona.amethyst.ui.theme.mediumImportanceLink
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
@@ -567,7 +547,7 @@ private fun StatusRow(
|
|||||||
Column(modifier = ReactionRowHeightChat) {
|
Column(modifier = ReactionRowHeightChat) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = ReactionRowHeightChat) {
|
Row(verticalAlignment = Alignment.CenterVertically, modifier = ReactionRowHeightChat) {
|
||||||
ChatTimeAgo(baseNote)
|
ChatTimeAgo(baseNote)
|
||||||
RelayBadges(baseNote, accountViewModel, nav = nav)
|
RelayBadgesHorizontal(baseNote, accountViewModel, nav = nav)
|
||||||
Spacer(modifier = DoubleHorzSpacer)
|
Spacer(modifier = DoubleHorzSpacer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -806,131 +786,3 @@ private fun DisplayMessageUsername(
|
|||||||
Spacer(modifier = StdHorzSpacer)
|
Spacer(modifier = StdHorzSpacer)
|
||||||
DrawPlayName(userDisplayName)
|
DrawPlayName(userDisplayName)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
|
||||||
val expanded = remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
RenderRelayList(baseNote, expanded, accountViewModel, nav)
|
|
||||||
|
|
||||||
RenderExpandButton(baseNote, expanded) {
|
|
||||||
ChatRelayExpandButton { expanded.value = true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
|
||||||
fun RenderRelayList(baseNote: Note, expanded: MutableState<Boolean>, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
|
||||||
val noteRelays by baseNote.live().relays.map {
|
|
||||||
it.note.relays
|
|
||||||
}.observeAsState(baseNote.relays)
|
|
||||||
|
|
||||||
FlowRow(StdStartPadding) {
|
|
||||||
val relaysToDisplay = remember(noteRelays, expanded.value) {
|
|
||||||
if (expanded.value) noteRelays else noteRelays.take(3)
|
|
||||||
}
|
|
||||||
relaysToDisplay.forEach {
|
|
||||||
RenderRelay(it, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RenderExpandButton(
|
|
||||||
baseNote: Note,
|
|
||||||
expanded: MutableState<Boolean>,
|
|
||||||
content: @Composable () -> Unit
|
|
||||||
) {
|
|
||||||
val showExpandButton by baseNote.live().relays.map {
|
|
||||||
it.note.relays.size > 3
|
|
||||||
}.observeAsState(baseNote.relays.size > 3)
|
|
||||||
|
|
||||||
if (showExpandButton && !expanded.value) {
|
|
||||||
content()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ChatRelayExpandButton(onClick: () -> Unit) {
|
|
||||||
IconButton(
|
|
||||||
modifier = Size15Modifier,
|
|
||||||
onClick = onClick
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.ChevronRight,
|
|
||||||
null,
|
|
||||||
modifier = Size15Modifier,
|
|
||||||
tint = MaterialTheme.colors.placeholderText
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
|
||||||
val iconUrl by remember(dirtyUrl) {
|
|
||||||
derivedStateOf {
|
|
||||||
val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/")
|
|
||||||
"https://$cleanUrl/favicon.ico"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
|
|
||||||
|
|
||||||
if (relayInfo != null) {
|
|
||||||
RelayInformationDialog(
|
|
||||||
onClose = {
|
|
||||||
relayInfo = null
|
|
||||||
},
|
|
||||||
relayInfo = relayInfo!!,
|
|
||||||
accountViewModel,
|
|
||||||
nav
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
val ripple = rememberRipple(bounded = false, radius = Size15dp)
|
|
||||||
|
|
||||||
val clickableModifier = remember(dirtyUrl) {
|
|
||||||
Modifier
|
|
||||||
.padding(1.dp)
|
|
||||||
.size(Size15dp)
|
|
||||||
.clickable(
|
|
||||||
role = Role.Button,
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
indication = ripple,
|
|
||||||
onClick = {
|
|
||||||
loadRelayInfo(dirtyUrl, context, scope) {
|
|
||||||
relayInfo = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = clickableModifier
|
|
||||||
) {
|
|
||||||
RenderRelayIcon(iconUrl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun RenderRelayIcon(iconUrl: String) {
|
|
||||||
val backgroundColor = MaterialTheme.colors.background
|
|
||||||
|
|
||||||
val iconModifier = remember {
|
|
||||||
Modifier
|
|
||||||
.size(Size13dp)
|
|
||||||
.clip(shape = CircleShape)
|
|
||||||
.background(backgroundColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
RobohashFallbackAsyncImage(
|
|
||||||
robot = iconUrl,
|
|
||||||
model = iconUrl,
|
|
||||||
contentDescription = stringResource(id = R.string.relay_icon),
|
|
||||||
colorFilter = RelayIconFilter,
|
|
||||||
modifier = iconModifier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.map
|
import androidx.lifecycle.map
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.model.UserMetadata
|
import com.vitorpamplona.amethyst.model.UserMetadata
|
||||||
import com.vitorpamplona.amethyst.service.Nip05Verifier
|
import com.vitorpamplona.amethyst.service.Nip05Verifier
|
||||||
@@ -39,14 +40,13 @@ import com.vitorpamplona.amethyst.ui.theme.Nip05
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun nip05VerificationAsAState(user: UserMetadata, pubkeyHex: String): MutableState<Boolean?> {
|
fun nip05VerificationAsAState(userMetadata: UserMetadata, pubkeyHex: String): MutableState<Boolean?> {
|
||||||
val nip05Verified = remember(user.nip05) {
|
val nip05Verified = remember(userMetadata.nip05) {
|
||||||
// starts with null if must verify or already filled in if verified in the last hour
|
// starts with null if must verify or already filled in if verified in the last hour
|
||||||
val default = if ((user.nip05LastVerificationTime ?: 0) > (Date().time / 1000 - 60 * 60)) { // 1hour
|
val default = if ((userMetadata.nip05LastVerificationTime ?: 0) > TimeUtils.oneHourAgo()) {
|
||||||
user.nip05Verified
|
userMetadata.nip05Verified
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@@ -55,23 +55,23 @@ fun nip05VerificationAsAState(user: UserMetadata, pubkeyHex: String): MutableSta
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nip05Verified.value == null) {
|
if (nip05Verified.value == null) {
|
||||||
LaunchedEffect(key1 = user.nip05) {
|
LaunchedEffect(key1 = userMetadata.nip05) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
user.nip05?.ifBlank { null }?.let { nip05 ->
|
userMetadata.nip05?.ifBlank { null }?.let { nip05 ->
|
||||||
Nip05Verifier().verifyNip05(
|
Nip05Verifier().verifyNip05(
|
||||||
nip05,
|
nip05,
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
// Marks user as verified
|
// Marks user as verified
|
||||||
if (it == pubkeyHex) {
|
if (it == pubkeyHex) {
|
||||||
user.nip05Verified = true
|
userMetadata.nip05Verified = true
|
||||||
user.nip05LastVerificationTime = Date().time / 1000
|
userMetadata.nip05LastVerificationTime = TimeUtils.now()
|
||||||
|
|
||||||
if (nip05Verified.value != true) {
|
if (nip05Verified.value != true) {
|
||||||
nip05Verified.value = true
|
nip05Verified.value = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
user.nip05Verified = false
|
userMetadata.nip05Verified = false
|
||||||
user.nip05LastVerificationTime = 0
|
userMetadata.nip05LastVerificationTime = 0
|
||||||
|
|
||||||
if (nip05Verified.value != false) {
|
if (nip05Verified.value != false) {
|
||||||
nip05Verified.value = false
|
nip05Verified.value = false
|
||||||
@@ -79,8 +79,8 @@ fun nip05VerificationAsAState(user: UserMetadata, pubkeyHex: String): MutableSta
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = {
|
onError = {
|
||||||
user.nip05LastVerificationTime = 0
|
userMetadata.nip05LastVerificationTime = 0
|
||||||
user.nip05Verified = false
|
userMetadata.nip05Verified = false
|
||||||
|
|
||||||
if (nip05Verified.value != false) {
|
if (nip05Verified.value != false) {
|
||||||
nip05Verified.value = false
|
nip05Verified.value = false
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
@@ -10,7 +9,6 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.border
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -28,8 +26,6 @@ import androidx.compose.foundation.shape.CircleShape
|
|||||||
import androidx.compose.foundation.shape.CutCornerShape
|
import androidx.compose.foundation.shape.CutCornerShape
|
||||||
import androidx.compose.foundation.text.ClickableText
|
import androidx.compose.foundation.text.ClickableText
|
||||||
import androidx.compose.material.Divider
|
import androidx.compose.material.Divider
|
||||||
import androidx.compose.material.DropdownMenu
|
|
||||||
import androidx.compose.material.DropdownMenuItem
|
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.LocalTextStyle
|
import androidx.compose.material.LocalTextStyle
|
||||||
@@ -38,12 +34,10 @@ import androidx.compose.material.Text
|
|||||||
import androidx.compose.material.darkColors
|
import androidx.compose.material.darkColors
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Bolt
|
import androidx.compose.material.icons.filled.Bolt
|
||||||
import androidx.compose.material.icons.filled.ExpandMore
|
|
||||||
import androidx.compose.material.icons.filled.Link
|
import androidx.compose.material.icons.filled.Link
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
import androidx.compose.material.icons.filled.PushPin
|
import androidx.compose.material.icons.filled.PushPin
|
||||||
import androidx.compose.material.lightColors
|
import androidx.compose.material.lightColors
|
||||||
import androidx.compose.material.ripple.rememberRipple
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -58,7 +52,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
import androidx.compose.ui.Alignment.Companion.CenterVertically
|
||||||
import androidx.compose.ui.Alignment.Companion.TopEnd
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
@@ -71,15 +64,12 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.semantics.Role
|
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.Dp
|
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.graphics.drawable.toBitmap
|
import androidx.core.graphics.drawable.toBitmap
|
||||||
import androidx.core.graphics.get
|
import androidx.core.graphics.get
|
||||||
import androidx.lifecycle.distinctUntilChanged
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
@@ -133,7 +123,6 @@ import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji
|
|||||||
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji
|
||||||
import com.vitorpamplona.amethyst.ui.components.LoadThumbAndThenVideoView
|
import com.vitorpamplona.amethyst.ui.components.LoadThumbAndThenVideoView
|
||||||
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
import com.vitorpamplona.amethyst.ui.components.SensitivityWarning
|
||||||
import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
|
import com.vitorpamplona.amethyst.ui.components.ShowMoreButton
|
||||||
@@ -154,7 +143,6 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader
|
|||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.JoinCommunityButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.JoinCommunityButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LeaveCommunityButton
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LeaveCommunityButton
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
|
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag
|
||||||
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange
|
||||||
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
|
||||||
@@ -165,9 +153,6 @@ import com.vitorpamplona.amethyst.ui.theme.HalfPadding
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
import com.vitorpamplona.amethyst.ui.theme.HalfStartPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
|
import com.vitorpamplona.amethyst.ui.theme.HalfVertSpacer
|
||||||
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
import com.vitorpamplona.amethyst.ui.theme.QuoteBorder
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconButtonModifier
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconModifier
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
|
import com.vitorpamplona.amethyst.ui.theme.Size24Modifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||||
@@ -3317,529 +3302,3 @@ fun CreateImageHeader(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
|
||||||
var expanded by remember { mutableStateOf(false) }
|
|
||||||
var showShowMore by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
var lazyRelayList by remember {
|
|
||||||
val baseNumber = baseNote.relays.map {
|
|
||||||
it.removePrefix("wss://").removePrefix("ws://")
|
|
||||||
}.toImmutableList()
|
|
||||||
|
|
||||||
mutableStateOf(baseNumber)
|
|
||||||
}
|
|
||||||
var shortRelayList by remember {
|
|
||||||
mutableStateOf(lazyRelayList.take(3).toImmutableList())
|
|
||||||
}
|
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
WatchRelayLists(baseNote) { relayList ->
|
|
||||||
if (!equalImmutableLists(relayList, lazyRelayList)) {
|
|
||||||
scope.launch(Dispatchers.Main) {
|
|
||||||
lazyRelayList = relayList
|
|
||||||
shortRelayList = relayList.take(3).toImmutableList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val nextShowMore = relayList.size > 3
|
|
||||||
if (nextShowMore != showShowMore) {
|
|
||||||
scope.launch(Dispatchers.Main) {
|
|
||||||
// only triggers recomposition when actually different
|
|
||||||
showShowMore = nextShowMore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(DoubleVertSpacer)
|
|
||||||
|
|
||||||
if (expanded) {
|
|
||||||
VerticalRelayPanelWithFlow(lazyRelayList, accountViewModel, nav)
|
|
||||||
} else {
|
|
||||||
VerticalRelayPanelWithFlow(shortRelayList, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showShowMore && !expanded) {
|
|
||||||
ShowMoreRelaysButton {
|
|
||||||
expanded = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun WatchRelayLists(baseNote: Note, onListChanges: (ImmutableList<String>) -> Unit) {
|
|
||||||
val noteRelaysState by baseNote.live().relays.observeAsState()
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = noteRelaysState) {
|
|
||||||
launch(Dispatchers.IO) {
|
|
||||||
val relayList = noteRelaysState?.note?.relays?.map {
|
|
||||||
it.removePrefix("wss://").removePrefix("ws://")
|
|
||||||
} ?: emptyList()
|
|
||||||
|
|
||||||
onListChanges(relayList.toImmutableList())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
|
||||||
@Stable
|
|
||||||
private fun VerticalRelayPanelWithFlow(
|
|
||||||
relays: ImmutableList<String>,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
nav: (String) -> Unit
|
|
||||||
) {
|
|
||||||
// FlowRow Seems to be a lot faster than LazyVerticalGrid
|
|
||||||
FlowRow() {
|
|
||||||
relays.forEach { url ->
|
|
||||||
RenderRelay(url, accountViewModel, nav)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ShowMoreRelaysButton(onClick: () -> Unit) {
|
|
||||||
Row(
|
|
||||||
modifier = ShowMoreRelaysButtonBoxModifer,
|
|
||||||
horizontalArrangement = Arrangement.Center,
|
|
||||||
verticalAlignment = Alignment.Top
|
|
||||||
) {
|
|
||||||
IconButton(
|
|
||||||
modifier = ShowMoreRelaysButtonIconButtonModifier,
|
|
||||||
onClick = onClick
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.ExpandMore,
|
|
||||||
null,
|
|
||||||
modifier = ShowMoreRelaysButtonIconModifier,
|
|
||||||
tint = MaterialTheme.colors.placeholderText
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun NoteAuthorPicture(
|
|
||||||
baseNote: Note,
|
|
||||||
nav: (String) -> Unit,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
size: Dp,
|
|
||||||
pictureModifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
NoteAuthorPicture(baseNote, size, accountViewModel, pictureModifier) {
|
|
||||||
nav("User/${it.pubkeyHex}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun NoteAuthorPicture(
|
|
||||||
baseNote: Note,
|
|
||||||
size: Dp,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onClick: ((User) -> Unit)? = null
|
|
||||||
) {
|
|
||||||
val author by baseNote.live().metadata.map {
|
|
||||||
it.note.author
|
|
||||||
}.distinctUntilChanged().observeAsState(baseNote.author)
|
|
||||||
|
|
||||||
Crossfade(targetState = author) {
|
|
||||||
if (it == null) {
|
|
||||||
DisplayBlankAuthor(size, modifier)
|
|
||||||
} else {
|
|
||||||
ClickableUserPicture(it, size, accountViewModel, modifier, onClick)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun DisplayBlankAuthor(size: Dp, modifier: Modifier = Modifier) {
|
|
||||||
val backgroundColor = MaterialTheme.colors.background
|
|
||||||
|
|
||||||
val nullModifier = remember {
|
|
||||||
modifier
|
|
||||||
.size(size)
|
|
||||||
.clip(shape = CircleShape)
|
|
||||||
.background(backgroundColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
RobohashAsyncImage(
|
|
||||||
robot = "authornotfound",
|
|
||||||
contentDescription = stringResource(R.string.unknown_author),
|
|
||||||
modifier = nullModifier
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun UserPicture(
|
|
||||||
user: User,
|
|
||||||
size: Dp,
|
|
||||||
pictureModifier: Modifier = remember { Modifier },
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
nav: (String) -> Unit
|
|
||||||
) {
|
|
||||||
val route by remember {
|
|
||||||
derivedStateOf {
|
|
||||||
"User/${user.pubkeyHex}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
ClickableUserPicture(
|
|
||||||
baseUser = user,
|
|
||||||
size = size,
|
|
||||||
accountViewModel = accountViewModel,
|
|
||||||
modifier = pictureModifier,
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
nav(route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
|
||||||
fun ClickableUserPicture(
|
|
||||||
baseUser: User,
|
|
||||||
size: Dp,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
modifier: Modifier = remember { Modifier },
|
|
||||||
onClick: ((User) -> Unit)? = null,
|
|
||||||
onLongClick: ((User) -> Unit)? = null
|
|
||||||
) {
|
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
|
||||||
val ripple = rememberRipple(bounded = false, radius = size)
|
|
||||||
|
|
||||||
// BaseUser is the same reference as accountState.user
|
|
||||||
val myModifier = remember {
|
|
||||||
if (onClick != null && onLongClick != null) {
|
|
||||||
Modifier
|
|
||||||
.size(size)
|
|
||||||
.combinedClickable(
|
|
||||||
onClick = { onClick(baseUser) },
|
|
||||||
onLongClick = { onLongClick(baseUser) },
|
|
||||||
role = Role.Button,
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
indication = ripple
|
|
||||||
)
|
|
||||||
} else if (onClick != null) {
|
|
||||||
Modifier
|
|
||||||
.size(size)
|
|
||||||
.clickable(
|
|
||||||
onClick = { onClick(baseUser) },
|
|
||||||
role = Role.Button,
|
|
||||||
interactionSource = interactionSource,
|
|
||||||
indication = ripple
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Modifier.size(size)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(modifier = myModifier, contentAlignment = TopEnd) {
|
|
||||||
BaseUserPicture(baseUser, size, accountViewModel, modifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun NonClickableUserPicture(
|
|
||||||
baseUser: User,
|
|
||||||
size: Dp,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
modifier: Modifier = remember { Modifier }
|
|
||||||
) {
|
|
||||||
val myBoxModifier = remember {
|
|
||||||
Modifier.size(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(myBoxModifier, contentAlignment = TopEnd) {
|
|
||||||
BaseUserPicture(baseUser, size, accountViewModel, modifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BaseUserPicture(
|
|
||||||
baseUser: User,
|
|
||||||
size: Dp,
|
|
||||||
accountViewModel: AccountViewModel,
|
|
||||||
modifier: Modifier = remember { Modifier }
|
|
||||||
) {
|
|
||||||
val userPubkey = remember {
|
|
||||||
baseUser.pubkeyHex
|
|
||||||
}
|
|
||||||
|
|
||||||
val userProfile by baseUser.live().metadata.map {
|
|
||||||
it.user.profilePicture()
|
|
||||||
}.distinctUntilChanged().observeAsState(baseUser.profilePicture())
|
|
||||||
|
|
||||||
val myBoxModifier = remember {
|
|
||||||
Modifier.size(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(myBoxModifier, contentAlignment = TopEnd) {
|
|
||||||
PictureAndFollowingMark(
|
|
||||||
userHex = userPubkey,
|
|
||||||
userPicture = userProfile,
|
|
||||||
size = size,
|
|
||||||
modifier = modifier,
|
|
||||||
accountViewModel = accountViewModel
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun PictureAndFollowingMark(
|
|
||||||
userHex: String,
|
|
||||||
userPicture: String?,
|
|
||||||
size: Dp,
|
|
||||||
modifier: Modifier,
|
|
||||||
accountViewModel: AccountViewModel
|
|
||||||
) {
|
|
||||||
val backgroundColor = MaterialTheme.colors.background
|
|
||||||
val myImageModifier = remember {
|
|
||||||
modifier
|
|
||||||
.size(size)
|
|
||||||
.clip(shape = CircleShape)
|
|
||||||
.background(backgroundColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
RobohashAsyncImageProxy(
|
|
||||||
robot = userHex,
|
|
||||||
model = userPicture,
|
|
||||||
contentDescription = stringResource(id = R.string.profile_image),
|
|
||||||
modifier = myImageModifier,
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
|
|
||||||
val myIconSize by remember(size) {
|
|
||||||
derivedStateOf {
|
|
||||||
size.div(3.5f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ObserveAndDisplayFollowingMark(userHex, myIconSize, accountViewModel)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ObserveAndDisplayFollowingMark(userHex: String, iconSize: Dp, accountViewModel: AccountViewModel) {
|
|
||||||
WatchFollows(userHex, accountViewModel) {
|
|
||||||
Crossfade(targetState = it) {
|
|
||||||
if (it) {
|
|
||||||
Box(contentAlignment = TopEnd) {
|
|
||||||
FollowingIcon(iconSize)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun WatchFollows(userHex: String, accountViewModel: AccountViewModel, onFollowChanges: @Composable (Boolean) -> Unit) {
|
|
||||||
val showFollowingMark by accountViewModel.userFollows.map {
|
|
||||||
it.user.isFollowingCached(userHex) || (userHex == accountViewModel.account.userProfile().pubkeyHex)
|
|
||||||
}.distinctUntilChanged().observeAsState(
|
|
||||||
accountViewModel.account.userProfile().isFollowingCached(userHex) || (userHex == accountViewModel.account.userProfile().pubkeyHex)
|
|
||||||
)
|
|
||||||
|
|
||||||
onFollowChanges(showFollowingMark)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun FollowingIcon(iconSize: Dp) {
|
|
||||||
val modifier = remember {
|
|
||||||
Modifier.size(iconSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(R.drawable.verified_follow_shield),
|
|
||||||
contentDescription = stringResource(id = R.string.following),
|
|
||||||
modifier = modifier,
|
|
||||||
tint = Color.Unspecified
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Immutable
|
|
||||||
data class DropDownParams(
|
|
||||||
val isFollowingAuthor: Boolean,
|
|
||||||
val isPrivateBookmarkNote: Boolean,
|
|
||||||
val isPublicBookmarkNote: Boolean,
|
|
||||||
val isLoggedUser: Boolean,
|
|
||||||
val isSensitive: Boolean,
|
|
||||||
val showSensitiveContent: Boolean?
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) {
|
|
||||||
var reportDialogShowing by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
var state by remember {
|
|
||||||
mutableStateOf<DropDownParams>(
|
|
||||||
DropDownParams(
|
|
||||||
isFollowingAuthor = false,
|
|
||||||
isPrivateBookmarkNote = false,
|
|
||||||
isPublicBookmarkNote = false,
|
|
||||||
isLoggedUser = false,
|
|
||||||
isSensitive = false,
|
|
||||||
showSensitiveContent = null
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
DropdownMenu(
|
|
||||||
expanded = popupExpanded,
|
|
||||||
onDismissRequest = onDismiss
|
|
||||||
) {
|
|
||||||
val clipboardManager = LocalClipboardManager.current
|
|
||||||
val appContext = LocalContext.current.applicationContext
|
|
||||||
val actContext = LocalContext.current
|
|
||||||
|
|
||||||
WatchBookmarksFollowsAndAccount(note, accountViewModel) { newState ->
|
|
||||||
if (state != newState) {
|
|
||||||
state = newState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
if (!state.isFollowingAuthor) {
|
|
||||||
DropdownMenuItem(onClick = {
|
|
||||||
accountViewModel.follow(
|
|
||||||
note.author ?: return@DropdownMenuItem
|
|
||||||
); onDismiss()
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.follow))
|
|
||||||
}
|
|
||||||
Divider()
|
|
||||||
}
|
|
||||||
DropdownMenuItem(
|
|
||||||
onClick = {
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
clipboardManager.setText(AnnotatedString(accountViewModel.decrypt(note) ?: ""))
|
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.copy_text))
|
|
||||||
}
|
|
||||||
DropdownMenuItem(
|
|
||||||
onClick = {
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
clipboardManager.setText(AnnotatedString("nostr:${note.author?.pubkeyNpub()}"))
|
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.copy_user_pubkey))
|
|
||||||
}
|
|
||||||
DropdownMenuItem(onClick = {
|
|
||||||
scope.launch(Dispatchers.IO) {
|
|
||||||
clipboardManager.setText(AnnotatedString("nostr:" + note.toNEvent()))
|
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.copy_note_id))
|
|
||||||
}
|
|
||||||
DropdownMenuItem(onClick = {
|
|
||||||
val sendIntent = Intent().apply {
|
|
||||||
action = Intent.ACTION_SEND
|
|
||||||
type = "text/plain"
|
|
||||||
putExtra(
|
|
||||||
Intent.EXTRA_TEXT,
|
|
||||||
externalLinkForNote(note)
|
|
||||||
)
|
|
||||||
putExtra(Intent.EXTRA_TITLE, actContext.getString(R.string.quick_action_share_browser_link))
|
|
||||||
}
|
|
||||||
|
|
||||||
val shareIntent = Intent.createChooser(sendIntent, appContext.getString(R.string.quick_action_share))
|
|
||||||
ContextCompat.startActivity(actContext, shareIntent, null)
|
|
||||||
onDismiss()
|
|
||||||
}) {
|
|
||||||
Text(stringResource(R.string.quick_action_share))
|
|
||||||
}
|
|
||||||
Divider()
|
|
||||||
if (state.isPrivateBookmarkNote) {
|
|
||||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.removePrivateBookmark(note); onDismiss() } }) {
|
|
||||||
Text(stringResource(R.string.remove_from_private_bookmarks))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.addPrivateBookmark(note); onDismiss() } }) {
|
|
||||||
Text(stringResource(R.string.add_to_private_bookmarks))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (state.isPublicBookmarkNote) {
|
|
||||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.removePublicBookmark(note); onDismiss() } }) {
|
|
||||||
Text(stringResource(R.string.remove_from_public_bookmarks))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.addPublicBookmark(note); onDismiss() } }) {
|
|
||||||
Text(stringResource(R.string.add_to_public_bookmarks))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Divider()
|
|
||||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.broadcast(note); onDismiss() } }) {
|
|
||||||
Text(stringResource(R.string.broadcast))
|
|
||||||
}
|
|
||||||
Divider()
|
|
||||||
if (state.isLoggedUser) {
|
|
||||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.delete(note); onDismiss() } }) {
|
|
||||||
Text(stringResource(R.string.request_deletion))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DropdownMenuItem(onClick = { reportDialogShowing = true }) {
|
|
||||||
Text("Block / Report")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Divider()
|
|
||||||
if (state.showSensitiveContent == null || state.showSensitiveContent == true) {
|
|
||||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.hideSensitiveContent(); onDismiss() } }) {
|
|
||||||
Text(stringResource(R.string.content_warning_hide_all_sensitive_content))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (state.showSensitiveContent == null || state.showSensitiveContent == false) {
|
|
||||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.disableContentWarnings(); onDismiss() } }) {
|
|
||||||
Text(stringResource(R.string.content_warning_show_all_sensitive_content))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (state.showSensitiveContent != null) {
|
|
||||||
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.seeContentWarnings(); onDismiss() } }) {
|
|
||||||
Text(stringResource(R.string.content_warning_see_warnings))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reportDialogShowing) {
|
|
||||||
ReportNoteDialog(note = note, accountViewModel = accountViewModel) {
|
|
||||||
reportDialogShowing = false
|
|
||||||
onDismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun WatchBookmarksFollowsAndAccount(note: Note, accountViewModel: AccountViewModel, onNew: (DropDownParams) -> Unit) {
|
|
||||||
val followState by accountViewModel.userProfile().live().follows.observeAsState()
|
|
||||||
val bookmarkState by accountViewModel.userProfile().live().bookmarks.observeAsState()
|
|
||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = followState, key2 = bookmarkState, key3 = accountState) {
|
|
||||||
launch(Dispatchers.IO) {
|
|
||||||
val newState = DropDownParams(
|
|
||||||
isFollowingAuthor = accountViewModel.isFollowing(note.author),
|
|
||||||
isPrivateBookmarkNote = accountViewModel.isInPrivateBookmarks(note),
|
|
||||||
isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note),
|
|
||||||
isLoggedUser = accountViewModel.isLoggedUser(note.author),
|
|
||||||
isSensitive = note.event?.isSensitive() ?: false,
|
|
||||||
showSensitiveContent = accountState?.account?.showSensitiveContent
|
|
||||||
)
|
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
|
||||||
onNew(
|
|
||||||
newState
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.TimeUtils
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.model.*
|
import com.vitorpamplona.amethyst.service.model.*
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
@@ -86,7 +87,7 @@ class PollNoteViewModel : ViewModel() {
|
|||||||
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
|
fun isVoteAmountAtomic() = valueMaximum != null && valueMinimum != null && valueMinimum == valueMaximum
|
||||||
|
|
||||||
fun isPollClosed(): Boolean = closedAt?.let { // allow 2 minute leeway for zap to propagate
|
fun isPollClosed(): Boolean = closedAt?.let { // allow 2 minute leeway for zap to propagate
|
||||||
pollNote?.createdAt()?.plus(it * (86400 + 120))!! < Date().time / 1000
|
pollNote?.createdAt()?.plus(it * (86400 + 120))!! < TimeUtils.now()
|
||||||
} == true
|
} == true
|
||||||
|
|
||||||
fun voteAmountPlaceHolderText(sats: String): String = if (valueMinimum == null && valueMaximum == null) {
|
fun voteAmountPlaceHolderText(sats: String): String = if (valueMinimum == null && valueMaximum == null) {
|
||||||
|
|||||||
@@ -0,0 +1,136 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ExpandMore
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonBoxModifer
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconButtonModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.ShowMoreRelaysButtonIconModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun RelayBadges(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
var showShowMore by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
var lazyRelayList by remember {
|
||||||
|
val baseNumber = baseNote.relays.map {
|
||||||
|
it.removePrefix("wss://").removePrefix("ws://")
|
||||||
|
}.toImmutableList()
|
||||||
|
|
||||||
|
mutableStateOf(baseNumber)
|
||||||
|
}
|
||||||
|
var shortRelayList by remember {
|
||||||
|
mutableStateOf(lazyRelayList.take(3).toImmutableList())
|
||||||
|
}
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
WatchRelayLists(baseNote) { relayList ->
|
||||||
|
if (!equalImmutableLists(relayList, lazyRelayList)) {
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
|
lazyRelayList = relayList
|
||||||
|
shortRelayList = relayList.take(3).toImmutableList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val nextShowMore = relayList.size > 3
|
||||||
|
if (nextShowMore != showShowMore) {
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
|
// only triggers recomposition when actually different
|
||||||
|
showShowMore = nextShowMore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(DoubleVertSpacer)
|
||||||
|
|
||||||
|
if (expanded) {
|
||||||
|
VerticalRelayPanelWithFlow(lazyRelayList, accountViewModel, nav)
|
||||||
|
} else {
|
||||||
|
VerticalRelayPanelWithFlow(shortRelayList, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showShowMore && !expanded) {
|
||||||
|
ShowMoreRelaysButton {
|
||||||
|
expanded = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun WatchRelayLists(baseNote: Note, onListChanges: (ImmutableList<String>) -> Unit) {
|
||||||
|
val noteRelaysState by baseNote.live().relays.observeAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = noteRelaysState) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
val relayList = noteRelaysState?.note?.relays?.map {
|
||||||
|
it.removePrefix("wss://").removePrefix("ws://")
|
||||||
|
} ?: emptyList()
|
||||||
|
|
||||||
|
onListChanges(relayList.toImmutableList())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
private fun VerticalRelayPanelWithFlow(
|
||||||
|
relays: ImmutableList<String>,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
// FlowRow Seems to be a lot faster than LazyVerticalGrid
|
||||||
|
FlowRow() {
|
||||||
|
relays.forEach { url ->
|
||||||
|
RenderRelay(url, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun ShowMoreRelaysButton(onClick: () -> Unit) {
|
||||||
|
Row(
|
||||||
|
modifier = ShowMoreRelaysButtonBoxModifer,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
verticalAlignment = Alignment.Top
|
||||||
|
) {
|
||||||
|
IconButton(
|
||||||
|
modifier = ShowMoreRelaysButtonIconButtonModifier,
|
||||||
|
onClick = onClick
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ExpandMore,
|
||||||
|
null,
|
||||||
|
modifier = ShowMoreRelaysButtonIconModifier,
|
||||||
|
tint = MaterialTheme.colors.placeholderText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ChevronRight
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.map
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.RelayInformation
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog
|
||||||
|
import com.vitorpamplona.amethyst.ui.actions.loadRelayInfo
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.RelayIconFilter
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size13dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size15Modifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.Size15dp
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
public fun RelayBadgesHorizontal(baseNote: Note, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
val expanded = remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
RenderRelayList(baseNote, expanded, accountViewModel, nav)
|
||||||
|
|
||||||
|
RenderExpandButton(baseNote, expanded) {
|
||||||
|
ChatRelayExpandButton { expanded.value = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun RenderRelayList(baseNote: Note, expanded: MutableState<Boolean>, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
val noteRelays by baseNote.live().relays.map {
|
||||||
|
it.note.relays
|
||||||
|
}.observeAsState(baseNote.relays)
|
||||||
|
|
||||||
|
FlowRow(StdStartPadding) {
|
||||||
|
val relaysToDisplay = remember(noteRelays, expanded.value) {
|
||||||
|
if (expanded.value) noteRelays else noteRelays.take(3)
|
||||||
|
}
|
||||||
|
relaysToDisplay.forEach {
|
||||||
|
RenderRelay(it, accountViewModel, nav)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderExpandButton(
|
||||||
|
baseNote: Note,
|
||||||
|
expanded: MutableState<Boolean>,
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
val showExpandButton by baseNote.live().relays.map {
|
||||||
|
it.note.relays.size > 3
|
||||||
|
}.observeAsState(baseNote.relays.size > 3)
|
||||||
|
|
||||||
|
if (showExpandButton && !expanded.value) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChatRelayExpandButton(onClick: () -> Unit) {
|
||||||
|
IconButton(
|
||||||
|
modifier = Size15Modifier,
|
||||||
|
onClick = onClick
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.ChevronRight,
|
||||||
|
null,
|
||||||
|
modifier = Size15Modifier,
|
||||||
|
tint = MaterialTheme.colors.placeholderText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RenderRelay(dirtyUrl: String, accountViewModel: AccountViewModel, nav: (String) -> Unit) {
|
||||||
|
val iconUrl by remember(dirtyUrl) {
|
||||||
|
derivedStateOf {
|
||||||
|
val cleanUrl = dirtyUrl.trim().removePrefix("wss://").removePrefix("ws://").removeSuffix("/")
|
||||||
|
"https://$cleanUrl/favicon.ico"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var relayInfo: RelayInformation? by remember { mutableStateOf(null) }
|
||||||
|
|
||||||
|
if (relayInfo != null) {
|
||||||
|
RelayInformationDialog(
|
||||||
|
onClose = {
|
||||||
|
relayInfo = null
|
||||||
|
},
|
||||||
|
relayInfo = relayInfo!!,
|
||||||
|
accountViewModel,
|
||||||
|
nav
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val ripple = rememberRipple(bounded = false, radius = Size15dp)
|
||||||
|
|
||||||
|
val clickableModifier = remember(dirtyUrl) {
|
||||||
|
Modifier
|
||||||
|
.padding(1.dp)
|
||||||
|
.size(Size15dp)
|
||||||
|
.clickable(
|
||||||
|
role = Role.Button,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = ripple,
|
||||||
|
onClick = {
|
||||||
|
loadRelayInfo(dirtyUrl, context, scope) {
|
||||||
|
relayInfo = it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = clickableModifier
|
||||||
|
) {
|
||||||
|
RenderRelayIcon(iconUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun RenderRelayIcon(iconUrl: String) {
|
||||||
|
val backgroundColor = MaterialTheme.colors.background
|
||||||
|
|
||||||
|
val iconModifier = remember {
|
||||||
|
Modifier
|
||||||
|
.size(Size13dp)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.background(backgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
RobohashFallbackAsyncImage(
|
||||||
|
robot = iconUrl,
|
||||||
|
model = iconUrl,
|
||||||
|
contentDescription = stringResource(id = R.string.relay_icon),
|
||||||
|
colorFilter = RelayIconFilter,
|
||||||
|
modifier = iconModifier
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,483 @@
|
|||||||
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Divider
|
||||||
|
import androidx.compose.material.DropdownMenu
|
||||||
|
import androidx.compose.material.DropdownMenuItem
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.ripple.rememberRipple
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
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.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.semantics.Role
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.lifecycle.distinctUntilChanged
|
||||||
|
import androidx.lifecycle.map
|
||||||
|
import com.vitorpamplona.amethyst.R
|
||||||
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage
|
||||||
|
import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.ReportNoteDialog
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NoteAuthorPicture(
|
||||||
|
baseNote: Note,
|
||||||
|
nav: (String) -> Unit,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
size: Dp,
|
||||||
|
pictureModifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
NoteAuthorPicture(baseNote, size, accountViewModel, pictureModifier) {
|
||||||
|
nav("User/${it.pubkeyHex}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NoteAuthorPicture(
|
||||||
|
baseNote: Note,
|
||||||
|
size: Dp,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
onClick: ((User) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
val author by baseNote.live().metadata.map {
|
||||||
|
it.note.author
|
||||||
|
}.distinctUntilChanged().observeAsState(baseNote.author)
|
||||||
|
|
||||||
|
Crossfade(targetState = author) {
|
||||||
|
if (it == null) {
|
||||||
|
DisplayBlankAuthor(size, modifier)
|
||||||
|
} else {
|
||||||
|
ClickableUserPicture(it, size, accountViewModel, modifier, onClick)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DisplayBlankAuthor(size: Dp, modifier: Modifier = Modifier) {
|
||||||
|
val backgroundColor = MaterialTheme.colors.background
|
||||||
|
|
||||||
|
val nullModifier = remember {
|
||||||
|
modifier
|
||||||
|
.size(size)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.background(backgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
RobohashAsyncImage(
|
||||||
|
robot = "authornotfound",
|
||||||
|
contentDescription = stringResource(R.string.unknown_author),
|
||||||
|
modifier = nullModifier
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun UserPicture(
|
||||||
|
user: User,
|
||||||
|
size: Dp,
|
||||||
|
pictureModifier: Modifier = remember { Modifier },
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
nav: (String) -> Unit
|
||||||
|
) {
|
||||||
|
val route by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
"User/${user.pubkeyHex}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
ClickableUserPicture(
|
||||||
|
baseUser = user,
|
||||||
|
size = size,
|
||||||
|
accountViewModel = accountViewModel,
|
||||||
|
modifier = pictureModifier,
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
nav(route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
@Composable
|
||||||
|
fun ClickableUserPicture(
|
||||||
|
baseUser: User,
|
||||||
|
size: Dp,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
modifier: Modifier = remember { Modifier },
|
||||||
|
onClick: ((User) -> Unit)? = null,
|
||||||
|
onLongClick: ((User) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val ripple = rememberRipple(bounded = false, radius = size)
|
||||||
|
|
||||||
|
// BaseUser is the same reference as accountState.user
|
||||||
|
val myModifier = remember {
|
||||||
|
if (onClick != null && onLongClick != null) {
|
||||||
|
Modifier
|
||||||
|
.size(size)
|
||||||
|
.combinedClickable(
|
||||||
|
onClick = { onClick(baseUser) },
|
||||||
|
onLongClick = { onLongClick(baseUser) },
|
||||||
|
role = Role.Button,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = ripple
|
||||||
|
)
|
||||||
|
} else if (onClick != null) {
|
||||||
|
Modifier
|
||||||
|
.size(size)
|
||||||
|
.clickable(
|
||||||
|
onClick = { onClick(baseUser) },
|
||||||
|
role = Role.Button,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
indication = ripple
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Modifier.size(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(modifier = myModifier, contentAlignment = Alignment.TopEnd) {
|
||||||
|
BaseUserPicture(baseUser, size, accountViewModel, modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NonClickableUserPicture(
|
||||||
|
baseUser: User,
|
||||||
|
size: Dp,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
modifier: Modifier = remember { Modifier }
|
||||||
|
) {
|
||||||
|
val myBoxModifier = remember {
|
||||||
|
Modifier.size(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(myBoxModifier, contentAlignment = Alignment.TopEnd) {
|
||||||
|
BaseUserPicture(baseUser, size, accountViewModel, modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BaseUserPicture(
|
||||||
|
baseUser: User,
|
||||||
|
size: Dp,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
modifier: Modifier = remember { Modifier }
|
||||||
|
) {
|
||||||
|
val myBoxModifier = remember {
|
||||||
|
Modifier.size(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(myBoxModifier, contentAlignment = Alignment.TopEnd) {
|
||||||
|
InnerBaseUserPicture(baseUser, size, accountViewModel, modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InnerBaseUserPicture(
|
||||||
|
baseUser: User,
|
||||||
|
size: Dp,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
|
modifier: Modifier
|
||||||
|
) {
|
||||||
|
val userProfile by baseUser.live().metadata.map {
|
||||||
|
it.user.profilePicture()
|
||||||
|
}.distinctUntilChanged().observeAsState(baseUser.profilePicture())
|
||||||
|
|
||||||
|
PictureAndFollowingMark(
|
||||||
|
userHex = baseUser.pubkeyHex,
|
||||||
|
userPicture = userProfile,
|
||||||
|
size = size,
|
||||||
|
modifier = modifier,
|
||||||
|
accountViewModel = accountViewModel
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PictureAndFollowingMark(
|
||||||
|
userHex: String,
|
||||||
|
userPicture: String?,
|
||||||
|
size: Dp,
|
||||||
|
modifier: Modifier,
|
||||||
|
accountViewModel: AccountViewModel
|
||||||
|
) {
|
||||||
|
val backgroundColor = MaterialTheme.colors.background
|
||||||
|
val myImageModifier = remember {
|
||||||
|
modifier
|
||||||
|
.size(size)
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
.background(backgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
RobohashAsyncImageProxy(
|
||||||
|
robot = userHex,
|
||||||
|
model = userPicture,
|
||||||
|
contentDescription = stringResource(id = R.string.profile_image),
|
||||||
|
modifier = myImageModifier,
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
|
||||||
|
val myIconSize by remember(size) {
|
||||||
|
derivedStateOf {
|
||||||
|
size.div(3.5f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ObserveAndDisplayFollowingMark(userHex, myIconSize, accountViewModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ObserveAndDisplayFollowingMark(userHex: String, iconSize: Dp, accountViewModel: AccountViewModel) {
|
||||||
|
WatchFollows(userHex, accountViewModel) { newFollowingState ->
|
||||||
|
Crossfade(targetState = newFollowingState) { following ->
|
||||||
|
if (following) {
|
||||||
|
Box(contentAlignment = Alignment.TopEnd) {
|
||||||
|
FollowingIcon(iconSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WatchFollows(userHex: String, accountViewModel: AccountViewModel, onFollowChanges: @Composable (Boolean) -> Unit) {
|
||||||
|
val showFollowingMark by accountViewModel.userFollows.map {
|
||||||
|
it.user.isFollowingCached(userHex) || (userHex == accountViewModel.account.userProfile().pubkeyHex)
|
||||||
|
}.distinctUntilChanged().observeAsState(
|
||||||
|
accountViewModel.account.userProfile().isFollowingCached(userHex) || (userHex == accountViewModel.account.userProfile().pubkeyHex)
|
||||||
|
)
|
||||||
|
|
||||||
|
onFollowChanges(showFollowingMark)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FollowingIcon(iconSize: Dp) {
|
||||||
|
val modifier = remember {
|
||||||
|
Modifier.size(iconSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(R.drawable.verified_follow_shield),
|
||||||
|
contentDescription = stringResource(id = R.string.following),
|
||||||
|
modifier = modifier,
|
||||||
|
tint = Color.Unspecified
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class DropDownParams(
|
||||||
|
val isFollowingAuthor: Boolean,
|
||||||
|
val isPrivateBookmarkNote: Boolean,
|
||||||
|
val isPublicBookmarkNote: Boolean,
|
||||||
|
val isLoggedUser: Boolean,
|
||||||
|
val isSensitive: Boolean,
|
||||||
|
val showSensitiveContent: Boolean?
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NoteDropDownMenu(note: Note, popupExpanded: Boolean, onDismiss: () -> Unit, accountViewModel: AccountViewModel) {
|
||||||
|
var reportDialogShowing by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
var state by remember {
|
||||||
|
mutableStateOf<DropDownParams>(
|
||||||
|
DropDownParams(
|
||||||
|
isFollowingAuthor = false,
|
||||||
|
isPrivateBookmarkNote = false,
|
||||||
|
isPublicBookmarkNote = false,
|
||||||
|
isLoggedUser = false,
|
||||||
|
isSensitive = false,
|
||||||
|
showSensitiveContent = null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = popupExpanded,
|
||||||
|
onDismissRequest = onDismiss
|
||||||
|
) {
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
val appContext = LocalContext.current.applicationContext
|
||||||
|
val actContext = LocalContext.current
|
||||||
|
|
||||||
|
WatchBookmarksFollowsAndAccount(note, accountViewModel) { newState ->
|
||||||
|
if (state != newState) {
|
||||||
|
state = newState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
if (!state.isFollowingAuthor) {
|
||||||
|
DropdownMenuItem(onClick = {
|
||||||
|
accountViewModel.follow(
|
||||||
|
note.author ?: return@DropdownMenuItem
|
||||||
|
); onDismiss()
|
||||||
|
}) {
|
||||||
|
Text(stringResource(R.string.follow))
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
}
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
clipboardManager.setText(AnnotatedString(accountViewModel.decrypt(note) ?: ""))
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.copy_text))
|
||||||
|
}
|
||||||
|
DropdownMenuItem(
|
||||||
|
onClick = {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
clipboardManager.setText(AnnotatedString("nostr:${note.author?.pubkeyNpub()}"))
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.copy_user_pubkey))
|
||||||
|
}
|
||||||
|
DropdownMenuItem(onClick = {
|
||||||
|
scope.launch(Dispatchers.IO) {
|
||||||
|
clipboardManager.setText(AnnotatedString("nostr:" + note.toNEvent()))
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Text(stringResource(R.string.copy_note_id))
|
||||||
|
}
|
||||||
|
DropdownMenuItem(onClick = {
|
||||||
|
val sendIntent = Intent().apply {
|
||||||
|
action = Intent.ACTION_SEND
|
||||||
|
type = "text/plain"
|
||||||
|
putExtra(
|
||||||
|
Intent.EXTRA_TEXT,
|
||||||
|
externalLinkForNote(note)
|
||||||
|
)
|
||||||
|
putExtra(Intent.EXTRA_TITLE, actContext.getString(R.string.quick_action_share_browser_link))
|
||||||
|
}
|
||||||
|
|
||||||
|
val shareIntent = Intent.createChooser(sendIntent, appContext.getString(R.string.quick_action_share))
|
||||||
|
ContextCompat.startActivity(actContext, shareIntent, null)
|
||||||
|
onDismiss()
|
||||||
|
}) {
|
||||||
|
Text(stringResource(R.string.quick_action_share))
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
if (state.isPrivateBookmarkNote) {
|
||||||
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.removePrivateBookmark(note); onDismiss() } }) {
|
||||||
|
Text(stringResource(R.string.remove_from_private_bookmarks))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.addPrivateBookmark(note); onDismiss() } }) {
|
||||||
|
Text(stringResource(R.string.add_to_private_bookmarks))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.isPublicBookmarkNote) {
|
||||||
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.removePublicBookmark(note); onDismiss() } }) {
|
||||||
|
Text(stringResource(R.string.remove_from_public_bookmarks))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.addPublicBookmark(note); onDismiss() } }) {
|
||||||
|
Text(stringResource(R.string.add_to_public_bookmarks))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.broadcast(note); onDismiss() } }) {
|
||||||
|
Text(stringResource(R.string.broadcast))
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
if (state.isLoggedUser) {
|
||||||
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.delete(note); onDismiss() } }) {
|
||||||
|
Text(stringResource(R.string.request_deletion))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DropdownMenuItem(onClick = { reportDialogShowing = true }) {
|
||||||
|
Text("Block / Report")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Divider()
|
||||||
|
if (state.showSensitiveContent == null || state.showSensitiveContent == true) {
|
||||||
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.hideSensitiveContent(); onDismiss() } }) {
|
||||||
|
Text(stringResource(R.string.content_warning_hide_all_sensitive_content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.showSensitiveContent == null || state.showSensitiveContent == false) {
|
||||||
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.disableContentWarnings(); onDismiss() } }) {
|
||||||
|
Text(stringResource(R.string.content_warning_show_all_sensitive_content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.showSensitiveContent != null) {
|
||||||
|
DropdownMenuItem(onClick = { scope.launch(Dispatchers.IO) { accountViewModel.seeContentWarnings(); onDismiss() } }) {
|
||||||
|
Text(stringResource(R.string.content_warning_see_warnings))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reportDialogShowing) {
|
||||||
|
ReportNoteDialog(note = note, accountViewModel = accountViewModel) {
|
||||||
|
reportDialogShowing = false
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun WatchBookmarksFollowsAndAccount(note: Note, accountViewModel: AccountViewModel, onNew: (DropDownParams) -> Unit) {
|
||||||
|
val followState by accountViewModel.userProfile().live().follows.observeAsState()
|
||||||
|
val bookmarkState by accountViewModel.userProfile().live().bookmarks.observeAsState()
|
||||||
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = followState, key2 = bookmarkState, key3 = accountState) {
|
||||||
|
launch(Dispatchers.IO) {
|
||||||
|
val newState = DropDownParams(
|
||||||
|
isFollowingAuthor = accountViewModel.isFollowing(note.author),
|
||||||
|
isPrivateBookmarkNote = accountViewModel.isInPrivateBookmarks(note),
|
||||||
|
isPublicBookmarkNote = accountViewModel.isInPublicBookmarks(note),
|
||||||
|
isLoggedUser = accountViewModel.isLoggedUser(note.author),
|
||||||
|
isSensitive = note.event?.isSensitive() ?: false,
|
||||||
|
showSensitiveContent = accountState?.account?.showSensitiveContent
|
||||||
|
)
|
||||||
|
|
||||||
|
launch(Dispatchers.Main) {
|
||||||
|
onNew(
|
||||||
|
newState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user