kopia lustrzana https://github.com/vitorpamplona/amethyst
Porównaj commity
42 Commity
638dba770d
...
0854bd34ff
Autor | SHA1 | Data |
---|---|---|
Vitor Pamplona | 0854bd34ff | |
Vitor Pamplona | 1738a775ef | |
Vitor Pamplona | a6953872ea | |
Vitor Pamplona | d48714456c | |
Vitor Pamplona | c25aad482b | |
Crowdin Bot | cbebfd263b | |
Vitor Pamplona | 89dbe82191 | |
Vitor Pamplona | 7bb72d0c2d | |
Vitor Pamplona | 9be4895080 | |
Crowdin Bot | 6250db01d1 | |
Vitor Pamplona | 48f9045f1b | |
Vitor Pamplona | 818ca7e39e | |
Vitor Pamplona | e8675b8e45 | |
David Kaspar | cef7e17447 | |
greenart7c3 | 6b15a0db8e | |
greenart7c3 | 50c5845a11 | |
Vitor Pamplona | 1b7ba3de01 | |
Vitor Pamplona | 712063f5d2 | |
Vitor Pamplona | d92f23e274 | |
Vitor Pamplona | 3b7f530c0b | |
Crowdin Bot | 623a8d377c | |
Vitor Pamplona | 79489d0b07 | |
Vitor Pamplona | 827512b225 | |
Vitor Pamplona | 6acfadeb9b | |
Vitor Pamplona | e159af2cd7 | |
Vitor Pamplona | 89c2e9d2e0 | |
Vitor Pamplona | 06f6ab6719 | |
Vitor Pamplona | 98c48e8b6b | |
Vitor Pamplona | 25cde455d8 | |
Vitor Pamplona | ef0fdf553c | |
Vitor Pamplona | 719b950272 | |
Vitor Pamplona | 2d02fad6b9 | |
Crowdin Bot | a39db5bf7b | |
Vitor Pamplona | 7fd37367fc | |
Vitor Pamplona | e1c134830e | |
Vitor Pamplona | 621d1c7731 | |
Vitor Pamplona | 7475143506 | |
Vitor Pamplona | 2509d639bd | |
Crowdin Bot | 85dd5cf698 | |
Vitor Pamplona | 274ce6ad77 | |
Vitor Pamplona | 0e8d2fc33a | |
Vitor Pamplona | b88723b68b |
|
@ -12,9 +12,9 @@ android {
|
||||||
applicationId "com.vitorpamplona.amethyst"
|
applicationId "com.vitorpamplona.amethyst"
|
||||||
minSdk 26
|
minSdk 26
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 362
|
versionCode 365
|
||||||
versionName "0.85.3"
|
versionName "0.86.2"
|
||||||
buildConfigField "String", "RELEASE_NOTES_ID", "\"d8da33fd13d129d86c53564aedefafbe3716f007c520431be4a8e488d3925afb\""
|
buildConfigField "String", "RELEASE_NOTES_ID", "\"a704a11334ed4fe6fc6ee6f8856f6f005da33644770616f1437f8b2b488b52b1\""
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
|
|
|
@ -108,11 +108,15 @@ import kotlinx.coroutines.DelicateCoroutinesApi
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.combineTransform
|
import kotlinx.coroutines.flow.combineTransform
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flattenMerge
|
import kotlinx.coroutines.flow.flattenMerge
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.flow.transformLatest
|
import kotlinx.coroutines.flow.transformLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -216,179 +220,114 @@ class Account(
|
||||||
val communities: ImmutableSet<String> = persistentSetOf(),
|
val communities: ImmutableSet<String> = persistentSetOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
class ListNameNotePair(val listName: String, val event: GeneralListEvent?)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
val liveKind3Follows: StateFlow<LiveFollowLists> by lazy {
|
val liveKind3FollowsFlow: Flow<LiveFollowLists> =
|
||||||
userProfile()
|
userProfile().flow().follows.stateFlow.transformLatest {
|
||||||
.live()
|
emit(
|
||||||
.follows
|
LiveFollowLists(
|
||||||
.asFlow()
|
it.user.cachedFollowingKeySet().toImmutableSet(),
|
||||||
.transformLatest {
|
it.user.cachedFollowingTagSet().toImmutableSet(),
|
||||||
emit(
|
it.user.cachedFollowingGeohashSet().toImmutableSet(),
|
||||||
LiveFollowLists(
|
it.user.cachedFollowingCommunitiesSet().toImmutableSet(),
|
||||||
userProfile().cachedFollowingKeySet().toImmutableSet(),
|
),
|
||||||
userProfile().cachedFollowingTagSet().toImmutableSet(),
|
)
|
||||||
userProfile().cachedFollowingGeohashSet().toImmutableSet(),
|
}
|
||||||
userProfile().cachedFollowingCommunitiesSet().toImmutableSet(),
|
|
||||||
),
|
val liveKind3Follows = liveKind3FollowsFlow.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
||||||
)
|
|
||||||
}
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
private val liveHomeList: Flow<ListNameNotePair> by lazy {
|
||||||
|
defaultHomeFollowList.flatMapLatest { listName ->
|
||||||
|
loadPeopleListFlowFromListName(listName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val liveHomeList: StateFlow<NoteState?> by lazy {
|
fun loadPeopleListFlowFromListName(listName: String): Flow<ListNameNotePair> {
|
||||||
defaultHomeFollowList
|
return if (listName != GLOBAL_FOLLOWS && listName != KIND3_FOLLOWS) {
|
||||||
.transformLatest {
|
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
|
||||||
if (it != GLOBAL_FOLLOWS && it != KIND3_FOLLOWS) {
|
note?.flow()?.metadata?.stateFlow?.mapLatest {
|
||||||
LocalCache.checkGetOrCreateAddressableNote(it)?.flow()?.metadata?.stateFlow?.let {
|
val noteEvent = it.note.event as? GeneralListEvent
|
||||||
emit(it)
|
ListNameNotePair(listName, noteEvent)
|
||||||
}
|
} ?: MutableStateFlow(ListNameNotePair(listName, null))
|
||||||
|
} else {
|
||||||
|
MutableStateFlow(ListNameNotePair(listName, null))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun combinePeopleListFlows(
|
||||||
|
kind3FollowsSource: Flow<LiveFollowLists>,
|
||||||
|
peopleListFollowsSource: Flow<ListNameNotePair>,
|
||||||
|
): Flow<LiveFollowLists?> {
|
||||||
|
return combineTransform(kind3FollowsSource, peopleListFollowsSource) { kind3Follows, peopleListFollows ->
|
||||||
|
if (peopleListFollows.listName == GLOBAL_FOLLOWS) {
|
||||||
|
emit(null)
|
||||||
|
} else if (peopleListFollows.listName == KIND3_FOLLOWS) {
|
||||||
|
emit(kind3Follows)
|
||||||
|
} else if (peopleListFollows.event == null) {
|
||||||
|
emit(LiveFollowLists())
|
||||||
|
} else {
|
||||||
|
val result = waitToDecrypt(peopleListFollows.event)
|
||||||
|
if (result == null) {
|
||||||
|
emit(LiveFollowLists())
|
||||||
|
} else {
|
||||||
|
emit(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.flattenMerge()
|
}
|
||||||
.stateIn(scope, SharingStarted.Eagerly, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val liveHomeFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
val liveHomeFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
||||||
combineTransform(defaultHomeFollowList, liveKind3Follows, liveHomeList) {
|
combinePeopleListFlows(liveKind3FollowsFlow, liveHomeList)
|
||||||
listName,
|
|
||||||
kind3Follows,
|
|
||||||
peopleListFollows,
|
|
||||||
->
|
|
||||||
if (listName == GLOBAL_FOLLOWS) {
|
|
||||||
emit(null)
|
|
||||||
} else if (listName == KIND3_FOLLOWS) {
|
|
||||||
emit(kind3Follows)
|
|
||||||
} else {
|
|
||||||
val result =
|
|
||||||
withTimeoutOrNull(1000) {
|
|
||||||
suspendCancellableCoroutine { continuation ->
|
|
||||||
decryptLiveFollows(peopleListFollows) { continuation.resume(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result?.let { emit(it) } ?: run { emit(LiveFollowLists()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val liveNotificationList: StateFlow<NoteState?> by lazy {
|
private val liveNotificationList: Flow<ListNameNotePair> by lazy {
|
||||||
defaultNotificationFollowList
|
defaultNotificationFollowList
|
||||||
.transformLatest {
|
.transformLatest { listName ->
|
||||||
if (it != GLOBAL_FOLLOWS && it != KIND3_FOLLOWS) {
|
emit(loadPeopleListFlowFromListName(listName))
|
||||||
LocalCache.checkGetOrCreateAddressableNote(it)?.flow()?.metadata?.stateFlow?.let {
|
}.flattenMerge()
|
||||||
emit(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flattenMerge()
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val liveNotificationFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
val liveNotificationFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
||||||
combineTransform(defaultNotificationFollowList, liveKind3Follows, liveNotificationList) {
|
combinePeopleListFlows(liveKind3FollowsFlow, liveNotificationList)
|
||||||
listName,
|
|
||||||
kind3Follows,
|
|
||||||
peopleListFollows,
|
|
||||||
->
|
|
||||||
if (listName == GLOBAL_FOLLOWS) {
|
|
||||||
emit(null)
|
|
||||||
} else if (listName == KIND3_FOLLOWS) {
|
|
||||||
emit(kind3Follows)
|
|
||||||
} else {
|
|
||||||
val result =
|
|
||||||
withTimeoutOrNull(1000) {
|
|
||||||
suspendCancellableCoroutine { continuation ->
|
|
||||||
decryptLiveFollows(peopleListFollows) { continuation.resume(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result?.let { emit(it) } ?: run { emit(LiveFollowLists()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val liveStoriesList: StateFlow<NoteState?> by lazy {
|
private val liveStoriesList: Flow<ListNameNotePair> by lazy {
|
||||||
defaultStoriesFollowList
|
defaultStoriesFollowList
|
||||||
.transformLatest {
|
.transformLatest { listName ->
|
||||||
if (it != GLOBAL_FOLLOWS && it != KIND3_FOLLOWS) {
|
emit(loadPeopleListFlowFromListName(listName))
|
||||||
LocalCache.checkGetOrCreateAddressableNote(it)?.flow()?.metadata?.stateFlow?.let {
|
}.flattenMerge()
|
||||||
emit(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flattenMerge()
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val liveStoriesFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
val liveStoriesFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
||||||
combineTransform(defaultStoriesFollowList, liveKind3Follows, liveStoriesList) {
|
combinePeopleListFlows(liveKind3FollowsFlow, liveStoriesList)
|
||||||
listName,
|
|
||||||
kind3Follows,
|
|
||||||
peopleListFollows,
|
|
||||||
->
|
|
||||||
if (listName == GLOBAL_FOLLOWS) {
|
|
||||||
emit(null)
|
|
||||||
} else if (listName == KIND3_FOLLOWS) {
|
|
||||||
emit(kind3Follows)
|
|
||||||
} else {
|
|
||||||
val result =
|
|
||||||
withTimeoutOrNull(1000) {
|
|
||||||
suspendCancellableCoroutine { continuation ->
|
|
||||||
decryptLiveFollows(peopleListFollows) { continuation.resume(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result?.let { emit(it) } ?: run { emit(LiveFollowLists()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
private val liveDiscoveryList: StateFlow<NoteState?> by lazy {
|
private val liveDiscoveryList: Flow<ListNameNotePair> by lazy {
|
||||||
defaultDiscoveryFollowList
|
defaultDiscoveryFollowList
|
||||||
.transformLatest {
|
.transformLatest { listName ->
|
||||||
if (it != GLOBAL_FOLLOWS && it != KIND3_FOLLOWS) {
|
emit(loadPeopleListFlowFromListName(listName))
|
||||||
LocalCache.checkGetOrCreateAddressableNote(it)?.flow()?.metadata?.stateFlow?.let {
|
}.flattenMerge()
|
||||||
emit(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flattenMerge()
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val liveDiscoveryFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
val liveDiscoveryFollowLists: StateFlow<LiveFollowLists?> by lazy {
|
||||||
combineTransform(defaultDiscoveryFollowList, liveKind3Follows, liveDiscoveryList) {
|
combinePeopleListFlows(liveKind3FollowsFlow, liveDiscoveryList)
|
||||||
listName,
|
|
||||||
kind3Follows,
|
|
||||||
peopleListFollows,
|
|
||||||
->
|
|
||||||
if (listName == GLOBAL_FOLLOWS) {
|
|
||||||
emit(null)
|
|
||||||
} else if (listName == KIND3_FOLLOWS) {
|
|
||||||
emit(kind3Follows)
|
|
||||||
} else {
|
|
||||||
val result =
|
|
||||||
withTimeoutOrNull(1000) {
|
|
||||||
suspendCancellableCoroutine { continuation ->
|
|
||||||
decryptLiveFollows(peopleListFollows) { continuation.resume(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result?.let { emit(it) } ?: run { emit(LiveFollowLists()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
.stateIn(scope, SharingStarted.Eagerly, LiveFollowLists())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptLiveFollows(
|
private fun decryptLiveFollows(
|
||||||
peopleListFollows: NoteState?,
|
listEvent: GeneralListEvent,
|
||||||
onReady: (LiveFollowLists) -> Unit,
|
onReady: (LiveFollowLists) -> Unit,
|
||||||
) {
|
) {
|
||||||
val listEvent = (peopleListFollows?.note?.event as? GeneralListEvent)
|
listEvent.privateTags(signer) { privateTagList ->
|
||||||
listEvent?.privateTags(signer) { privateTagList ->
|
|
||||||
onReady(
|
onReady(
|
||||||
LiveFollowLists(
|
LiveFollowLists(
|
||||||
users =
|
users =
|
||||||
|
@ -406,6 +345,16 @@ class Account(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun waitToDecrypt(peopleListFollows: GeneralListEvent): LiveFollowLists? {
|
||||||
|
return withTimeoutOrNull(1000) {
|
||||||
|
suspendCancellableCoroutine { continuation ->
|
||||||
|
decryptLiveFollows(peopleListFollows) {
|
||||||
|
continuation.resume(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class LiveHiddenUsers(
|
data class LiveHiddenUsers(
|
||||||
val hiddenUsers: ImmutableSet<String>,
|
val hiddenUsers: ImmutableSet<String>,
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.util.Log
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
|
import com.vitorpamplona.amethyst.commons.data.DeletionIndex
|
||||||
import com.vitorpamplona.amethyst.commons.data.LargeCache
|
import com.vitorpamplona.amethyst.commons.data.LargeCache
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.service.relays.Relay
|
import com.vitorpamplona.amethyst.service.relays.Relay
|
||||||
|
@ -133,6 +134,8 @@ object LocalCache {
|
||||||
val channels = LargeCache<HexKey, Channel>()
|
val channels = LargeCache<HexKey, Channel>()
|
||||||
val awaitingPaymentRequests = ConcurrentHashMap<HexKey, Pair<Note?, (LnZapPaymentResponseEvent) -> Unit>>(10)
|
val awaitingPaymentRequests = ConcurrentHashMap<HexKey, Pair<Note?, (LnZapPaymentResponseEvent) -> Unit>>(10)
|
||||||
|
|
||||||
|
val deletionIndex = DeletionIndex()
|
||||||
|
|
||||||
fun checkGetOrCreateUser(key: String): User? {
|
fun checkGetOrCreateUser(key: String): User? {
|
||||||
// checkNotInMainThread()
|
// checkNotInMainThread()
|
||||||
|
|
||||||
|
@ -956,52 +959,53 @@ object LocalCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun consume(event: DeletionEvent) {
|
fun consume(event: DeletionEvent) {
|
||||||
var deletedAtLeastOne = false
|
if (deletionIndex.add(event)) {
|
||||||
|
var deletedAtLeastOne = false
|
||||||
|
|
||||||
event
|
event.deleteEvents()
|
||||||
.deleteEvents()
|
.mapNotNull { getNoteIfExists(it) }
|
||||||
.mapNotNull { getNoteIfExists(it) }
|
.forEach { deleteNote ->
|
||||||
.forEach { deleteNote ->
|
// must be the same author
|
||||||
// must be the same author
|
if (deleteNote.author?.pubkeyHex == event.pubKey) {
|
||||||
if (deleteNote.author?.pubkeyHex == event.pubKey) {
|
// reverts the add
|
||||||
// reverts the add
|
deleteNote(deleteNote)
|
||||||
deleteNote(deleteNote)
|
|
||||||
|
|
||||||
deletedAtLeastOne = true
|
deletedAtLeastOne = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val addressList = event.deleteAddressTags()
|
||||||
|
val addressSet = addressList.toSet()
|
||||||
|
|
||||||
|
addressList
|
||||||
|
.mapNotNull { getAddressableNoteIfExists(it) }
|
||||||
|
.forEach { deleteNote ->
|
||||||
|
// must be the same author
|
||||||
|
if (deleteNote.author?.pubkeyHex == event.pubKey && (deleteNote.createdAt() ?: 0) <= event.createdAt) {
|
||||||
|
// Counts the replies
|
||||||
|
deleteNote(deleteNote)
|
||||||
|
|
||||||
|
addressables.remove(deleteNote.idHex)
|
||||||
|
|
||||||
|
deletedAtLeastOne = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
notes.forEach { key, note ->
|
||||||
|
val noteEvent = note.event
|
||||||
|
if (noteEvent is AddressableEvent && noteEvent.addressTag() in addressSet) {
|
||||||
|
if (noteEvent.pubKey() == event.pubKey && noteEvent.createdAt() <= event.createdAt) {
|
||||||
|
deleteNote(note)
|
||||||
|
deletedAtLeastOne = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val addressList = event.deleteAddresses()
|
if (deletedAtLeastOne) {
|
||||||
val addressSet = addressList.toSet()
|
val note = Note(event.id)
|
||||||
|
note.loadEvent(event, getOrCreateUser(event.pubKey), emptyList())
|
||||||
addressList
|
refreshObservers(note)
|
||||||
.mapNotNull { getAddressableNoteIfExists(it.toTag()) }
|
|
||||||
.forEach { deleteNote ->
|
|
||||||
// must be the same author
|
|
||||||
if (deleteNote.author?.pubkeyHex == event.pubKey && (deleteNote.createdAt() ?: 0) < event.createdAt) {
|
|
||||||
// Counts the replies
|
|
||||||
deleteNote(deleteNote)
|
|
||||||
|
|
||||||
addressables.remove(deleteNote.idHex)
|
|
||||||
|
|
||||||
deletedAtLeastOne = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
notes.forEach { key, note ->
|
|
||||||
val noteEvent = note.event
|
|
||||||
if (noteEvent is AddressableEvent && noteEvent.address() in addressSet) {
|
|
||||||
if (noteEvent.pubKey() == event.pubKey && noteEvent.createdAt() <= event.createdAt) {
|
|
||||||
deleteNote(note)
|
|
||||||
deletedAtLeastOne = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deletedAtLeastOne) {
|
|
||||||
val note = Note(event.id)
|
|
||||||
note.loadEvent(event, getOrCreateUser(event.pubKey), emptyList())
|
|
||||||
refreshObservers(note)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2210,6 +2214,8 @@ object LocalCache {
|
||||||
event: Event,
|
event: Event,
|
||||||
relay: Relay?,
|
relay: Relay?,
|
||||||
) {
|
) {
|
||||||
|
if (deletionIndex.hasBeenDeleted(event)) return
|
||||||
|
|
||||||
checkNotInMainThread()
|
checkNotInMainThread()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -2306,6 +2312,10 @@ object LocalCache {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasConsumed(notificationEvent: Event): Boolean {
|
||||||
|
return notes.containsKey(notificationEvent.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
|
|
|
@ -127,6 +127,7 @@ class User(val pubkeyHex: String) {
|
||||||
|
|
||||||
// Update following of the current user
|
// Update following of the current user
|
||||||
liveSet?.innerFollows?.invalidateData()
|
liveSet?.innerFollows?.invalidateData()
|
||||||
|
flowSet?.follows?.invalidateData()
|
||||||
|
|
||||||
// Update Followers of the past user list
|
// Update Followers of the past user list
|
||||||
// Update Followers of the new contact list
|
// Update Followers of the new contact list
|
||||||
|
@ -474,14 +475,16 @@ class User(val pubkeyHex: String) {
|
||||||
@Stable
|
@Stable
|
||||||
class UserFlowSet(u: User) {
|
class UserFlowSet(u: User) {
|
||||||
// Observers line up here.
|
// Observers line up here.
|
||||||
|
val follows = UserBundledRefresherFlow(u)
|
||||||
val relays = UserBundledRefresherFlow(u)
|
val relays = UserBundledRefresherFlow(u)
|
||||||
|
|
||||||
fun isInUse(): Boolean {
|
fun isInUse(): Boolean {
|
||||||
return relays.stateFlow.subscriptionCount.value > 0
|
return relays.stateFlow.subscriptionCount.value > 0 || follows.stateFlow.subscriptionCount.value > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
fun destroy() {
|
fun destroy() {
|
||||||
relays.destroy()
|
relays.destroy()
|
||||||
|
follows.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,15 +64,17 @@ class EventNotificationConsumer(private val applicationContext: Context) {
|
||||||
account: Account,
|
account: Account,
|
||||||
) {
|
) {
|
||||||
pushWrappedEvent.cachedGift(account.signer) { notificationEvent ->
|
pushWrappedEvent.cachedGift(account.signer) { notificationEvent ->
|
||||||
LocalCache.justConsume(notificationEvent, null)
|
if (!LocalCache.hasConsumed(notificationEvent) && LocalCache.justVerify(notificationEvent)) {
|
||||||
|
unwrapAndConsume(notificationEvent, account) { innerEvent ->
|
||||||
unwrapAndConsume(notificationEvent, account) { innerEvent ->
|
if (!LocalCache.hasConsumed(innerEvent)) {
|
||||||
if (innerEvent is PrivateDmEvent) {
|
if (innerEvent is PrivateDmEvent) {
|
||||||
notify(innerEvent, account)
|
notify(innerEvent, account)
|
||||||
} else if (innerEvent is LnZapEvent) {
|
} else if (innerEvent is LnZapEvent) {
|
||||||
notify(innerEvent, account)
|
notify(innerEvent, account)
|
||||||
} else if (innerEvent is ChatMessageEvent) {
|
} else if (innerEvent is ChatMessageEvent) {
|
||||||
notify(innerEvent, account)
|
notify(innerEvent, account)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -353,9 +353,14 @@ class Relay(
|
||||||
if (read) {
|
if (read) {
|
||||||
if (isConnected()) {
|
if (isConnected()) {
|
||||||
if (isReady) {
|
if (isReady) {
|
||||||
if (filters.isNotEmpty()) {
|
val relayFilters =
|
||||||
|
filters.filter { filter ->
|
||||||
|
activeTypes.any { it in filter.types }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relayFilters.isNotEmpty()) {
|
||||||
val request =
|
val request =
|
||||||
filters.joinToStringLimited(
|
relayFilters.joinToStringLimited(
|
||||||
separator = ",",
|
separator = ",",
|
||||||
limit = 20,
|
limit = 20,
|
||||||
prefix = """["REQ","$requestId",""",
|
prefix = """["REQ","$requestId",""",
|
||||||
|
@ -423,12 +428,7 @@ class Relay(
|
||||||
fun renewFilters() {
|
fun renewFilters() {
|
||||||
// Force update all filters after AUTH.
|
// Force update all filters after AUTH.
|
||||||
Client.allSubscriptions().forEach {
|
Client.allSubscriptions().forEach {
|
||||||
val filters =
|
sendFilter(requestId = it.key, it.value)
|
||||||
it.value.filter { filter ->
|
|
||||||
activeTypes.any { it in filter.types }
|
|
||||||
}
|
|
||||||
|
|
||||||
sendFilter(requestId = it.key, filters)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,9 @@ object RelayPool : Relay.Listener {
|
||||||
subscriptionId: String,
|
subscriptionId: String,
|
||||||
filters: List<TypedFilter>,
|
filters: List<TypedFilter>,
|
||||||
) {
|
) {
|
||||||
relays.forEach { it.sendFilter(subscriptionId, filters) }
|
relays.forEach { relay ->
|
||||||
|
relay.sendFilter(subscriptionId, filters)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun connectAndSendFiltersIfDisconnected() {
|
fun connectAndSendFiltersIfDisconnected() {
|
||||||
|
|
|
@ -235,7 +235,12 @@ fun NewPostView(
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog(
|
Dialog(
|
||||||
onDismissRequest = { onClose() },
|
onDismissRequest = {
|
||||||
|
scope.launch {
|
||||||
|
postViewModel.sendDraftSync(relayList = relayList)
|
||||||
|
onClose()
|
||||||
|
}
|
||||||
|
},
|
||||||
properties =
|
properties =
|
||||||
DialogProperties(
|
DialogProperties(
|
||||||
usePlatformDefaultWidth = false,
|
usePlatformDefaultWidth = false,
|
||||||
|
@ -294,8 +299,9 @@ fun NewPostView(
|
||||||
Spacer(modifier = StdHorzSpacer)
|
Spacer(modifier = StdHorzSpacer)
|
||||||
CloseButton(
|
CloseButton(
|
||||||
onPress = {
|
onPress = {
|
||||||
postViewModel.cancel()
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
postViewModel.sendDraftSync(relayList = relayList)
|
||||||
|
postViewModel.cancel()
|
||||||
delay(100)
|
delay(100)
|
||||||
onClose()
|
onClose()
|
||||||
}
|
}
|
||||||
|
@ -1096,8 +1102,12 @@ fun FowardZapTo(
|
||||||
text = stringResource(R.string.zap_split_title),
|
text = stringResource(R.string.zap_split_title),
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
fontWeight = FontWeight.W500,
|
fontWeight = FontWeight.W500,
|
||||||
modifier = Modifier.padding(start = 10.dp),
|
modifier = Modifier.padding(horizontal = 10.dp).weight(1f),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
OutlinedButton(onClick = { postViewModel.updateZapFromText() }) {
|
||||||
|
Text(text = stringResource(R.string.load_from_text))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider(thickness = DividerThickness)
|
HorizontalDivider(thickness = DividerThickness)
|
||||||
|
@ -1133,7 +1143,7 @@ fun FowardZapTo(
|
||||||
Slider(
|
Slider(
|
||||||
value = splitItem.percentage,
|
value = splitItem.percentage,
|
||||||
onValueChange = { sliderValue ->
|
onValueChange = { sliderValue ->
|
||||||
val rounded = (round(sliderValue * 20)) / 20.0f
|
val rounded = (round(sliderValue * 100)) / 100.0f
|
||||||
postViewModel.updateZapPercentage(index, rounded)
|
postViewModel.updateZapPercentage(index, rounded)
|
||||||
},
|
},
|
||||||
modifier = Modifier.weight(1.5f),
|
modifier = Modifier.weight(1.5f),
|
||||||
|
|
|
@ -77,6 +77,7 @@ import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
enum class UserSuggestionAnchor {
|
enum class UserSuggestionAnchor {
|
||||||
|
@ -200,12 +201,16 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
val noteAuthor = draft?.author
|
val noteAuthor = draft?.author
|
||||||
|
|
||||||
if (draft != null && noteEvent is DraftEvent && noteAuthor != null) {
|
if (draft != null && noteEvent is DraftEvent && noteAuthor != null) {
|
||||||
accountViewModel.createTempDraftNote(noteEvent, noteAuthor) { innerNote ->
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val oldTag = (draft.event as? AddressableEvent)?.dTag()
|
accountViewModel.createTempDraftNote(noteEvent) { innerNote ->
|
||||||
if (oldTag != null) {
|
if (innerNote != null) {
|
||||||
draftTag = oldTag
|
val oldTag = (draft.event as? AddressableEvent)?.dTag()
|
||||||
|
if (oldTag != null) {
|
||||||
|
draftTag = oldTag
|
||||||
|
}
|
||||||
|
loadFromDraft(innerNote, accountViewModel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loadFromDraft(innerNote, accountViewModel)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
originalNote = replyingTo
|
originalNote = replyingTo
|
||||||
|
@ -442,18 +447,22 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendDraft(relayList: List<Relay>? = null) {
|
fun sendDraft(relayList: List<Relay>? = null) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch {
|
||||||
innerSendPost(relayList, draftTag)
|
sendDraftSync(relayList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun sendDraftSync(relayList: List<Relay>? = null) {
|
||||||
|
innerSendPost(relayList, draftTag)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun innerSendPost(
|
private suspend fun innerSendPost(
|
||||||
relayList: List<Relay>? = null,
|
relayList: List<Relay>? = null,
|
||||||
localDraft: String?,
|
localDraft: String?,
|
||||||
) {
|
) = withContext(Dispatchers.IO) {
|
||||||
if (accountViewModel == null) {
|
if (accountViewModel == null) {
|
||||||
cancel()
|
cancel()
|
||||||
return
|
return@withContext
|
||||||
}
|
}
|
||||||
|
|
||||||
val tagger = NewMessageTagger(message.text, pTags, eTags, originalNote?.channelHex(), accountViewModel!!)
|
val tagger = NewMessageTagger(message.text, pTags, eTags, originalNote?.channelHex(), accountViewModel!!)
|
||||||
|
@ -919,6 +928,7 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
compareBy(
|
compareBy(
|
||||||
{ account?.isFollowing(it) },
|
{ account?.isFollowing(it) },
|
||||||
{ it.toBestDisplayName() },
|
{ it.toBestDisplayName() },
|
||||||
|
{ it.pubkeyHex },
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.reversed()
|
.reversed()
|
||||||
|
@ -928,7 +938,6 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
userSuggestions = emptyList()
|
userSuggestions = emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
saveDraft()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun autocompleteWithUser(item: User) {
|
open fun autocompleteWithUser(item: User) {
|
||||||
|
@ -947,16 +956,6 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.FORWARD_ZAPS) {
|
||||||
forwardZapTo.addItem(item)
|
forwardZapTo.addItem(item)
|
||||||
forwardZapToEditting = TextFieldValue("")
|
forwardZapToEditting = TextFieldValue("")
|
||||||
/*
|
|
||||||
val lastWord = forwardZapToEditting.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
|
||||||
val lastWordStart = it.end - lastWord.length
|
|
||||||
val wordToInsert = "@${item.pubkeyNpub()}"
|
|
||||||
forwardZapTo = item
|
|
||||||
|
|
||||||
forwardZapToEditting = TextFieldValue(
|
|
||||||
forwardZapToEditting.text.replaceRange(lastWordStart, it.end, wordToInsert),
|
|
||||||
TextRange(lastWordStart + wordToInsert.length, lastWordStart + wordToInsert.length)
|
|
||||||
)*/
|
|
||||||
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.TO_USERS) {
|
} else if (userSuggestionsMainMessage == UserSuggestionAnchor.TO_USERS) {
|
||||||
val lastWord =
|
val lastWord =
|
||||||
toUsers.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
toUsers.text.substring(0, it.end).substringAfterLast("\n").substringAfterLast(" ")
|
||||||
|
@ -1199,6 +1198,18 @@ open class NewPostViewModel() : ViewModel() {
|
||||||
forwardZapTo.updatePercentage(index, sliderValue)
|
forwardZapTo.updatePercentage(index, sliderValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateZapFromText() {
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
val tagger = NewMessageTagger(message.text, emptyList(), emptyList(), null, accountViewModel!!)
|
||||||
|
tagger.run()
|
||||||
|
tagger.pTags?.forEach { taggedUser ->
|
||||||
|
if (!forwardZapTo.items.any { it.key == taggedUser }) {
|
||||||
|
forwardZapTo.addItem(taggedUser)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun updateZapRaiserAmount(newAmount: Long?) {
|
fun updateZapRaiserAmount(newAmount: Long?) {
|
||||||
zapRaiserAmount = newAmount
|
zapRaiserAmount = newAmount
|
||||||
saveDraft()
|
saveDraft()
|
||||||
|
|
|
@ -900,7 +900,15 @@ fun EditableServerConfig(
|
||||||
onClick = {
|
onClick = {
|
||||||
if (url.isNotBlank() && url != "/") {
|
if (url.isNotBlank() && url != "/") {
|
||||||
var addedWSS =
|
var addedWSS =
|
||||||
if (!url.startsWith("wss://") && !url.startsWith("ws://")) "wss://$url" else url
|
if (!url.startsWith("wss://") && !url.startsWith("ws://")) {
|
||||||
|
if (url.endsWith(".onion") || url.endsWith(".onion/")) {
|
||||||
|
"ws://$url"
|
||||||
|
} else {
|
||||||
|
"wss://$url"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
url
|
||||||
|
}
|
||||||
if (url.endsWith("/")) addedWSS = addedWSS.dropLast(1)
|
if (url.endsWith("/")) addedWSS = addedWSS.dropLast(1)
|
||||||
onNewRelay(RelaySetupInfo(addedWSS, read, write, feedTypes = FeedType.values().toSet()))
|
onNewRelay(RelaySetupInfo(addedWSS, read, write, feedTypes = FeedType.values().toSet()))
|
||||||
url = ""
|
url = ""
|
||||||
|
|
|
@ -53,7 +53,7 @@ import com.vitorpamplona.amethyst.ui.theme.secondaryButtonBackground
|
||||||
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
import com.vitorpamplona.quartz.events.ImmutableListOfLists
|
||||||
|
|
||||||
object ShowFullTextCache {
|
object ShowFullTextCache {
|
||||||
val cache = LruCache<String, Boolean>(20)
|
val cache = LruCache<String, Boolean>(10)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.note
|
package com.vitorpamplona.amethyst.ui.note
|
||||||
|
|
||||||
import androidx.compose.animation.animateContentSize
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
|
@ -305,7 +304,6 @@ private fun RenderBubble(
|
||||||
bubbleSize.intValue = it.width
|
bubbleSize.intValue = it.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animateContentSize()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(modifier = bubbleModifier) {
|
Column(modifier = bubbleModifier) {
|
||||||
|
|
|
@ -562,7 +562,7 @@ private fun RenderNoteRow(
|
||||||
is AppDefinitionEvent -> RenderAppDefinition(baseNote, accountViewModel, nav)
|
is AppDefinitionEvent -> RenderAppDefinition(baseNote, accountViewModel, nav)
|
||||||
is AudioTrackEvent -> RenderAudioTrack(baseNote, accountViewModel, nav)
|
is AudioTrackEvent -> RenderAudioTrack(baseNote, accountViewModel, nav)
|
||||||
is AudioHeaderEvent -> RenderAudioHeader(baseNote, accountViewModel, nav)
|
is AudioHeaderEvent -> RenderAudioHeader(baseNote, accountViewModel, nav)
|
||||||
is DraftEvent -> RenderDraft(baseNote, backgroundColor, accountViewModel, nav)
|
is DraftEvent -> RenderDraft(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
is ReactionEvent -> RenderReaction(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
is ReactionEvent -> RenderReaction(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
is RepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
is RepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
is GenericRepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
is GenericRepostEvent -> RenderRepost(baseNote, quotesLeft, backgroundColor, accountViewModel, nav)
|
||||||
|
@ -735,6 +735,7 @@ fun ObserveDraftEvent(
|
||||||
@Composable
|
@Composable
|
||||||
fun RenderDraft(
|
fun RenderDraft(
|
||||||
note: Note,
|
note: Note,
|
||||||
|
quotesLeft: Int,
|
||||||
backgroundColor: MutableState<Color>,
|
backgroundColor: MutableState<Color>,
|
||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
|
@ -748,7 +749,7 @@ fun RenderDraft(
|
||||||
makeItShort = false,
|
makeItShort = false,
|
||||||
canPreview = true,
|
canPreview = true,
|
||||||
editState = edits,
|
editState = edits,
|
||||||
quotesLeft = 3,
|
quotesLeft = quotesLeft,
|
||||||
unPackReply = true,
|
unPackReply = true,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
nav = nav,
|
||||||
|
|
|
@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.screen
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
@ -32,18 +31,13 @@ import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.pullrefresh.PullRefreshIndicator
|
|
||||||
import androidx.compose.material3.pullrefresh.pullRefresh
|
|
||||||
import androidx.compose.material3.pullrefresh.rememberPullRefreshState
|
|
||||||
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
|
||||||
import androidx.compose.runtime.getValue
|
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.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
@ -68,30 +62,8 @@ fun RefreshableCardView(
|
||||||
scrollStateKey: String? = null,
|
scrollStateKey: String? = null,
|
||||||
enablePullRefresh: Boolean = true,
|
enablePullRefresh: Boolean = true,
|
||||||
) {
|
) {
|
||||||
var refreshing by remember { mutableStateOf(false) }
|
RefresheableBox(viewModel, enablePullRefresh) {
|
||||||
val pullRefreshState =
|
|
||||||
rememberPullRefreshState(
|
|
||||||
refreshing,
|
|
||||||
onRefresh = {
|
|
||||||
refreshing = true
|
|
||||||
viewModel.invalidateData()
|
|
||||||
refreshing = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
val modifier =
|
|
||||||
if (enablePullRefresh) {
|
|
||||||
Modifier.fillMaxSize().pullRefresh(pullRefreshState)
|
|
||||||
} else {
|
|
||||||
Modifier.fillMaxSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(modifier) {
|
|
||||||
SaveableCardFeedState(viewModel, accountViewModel, nav, routeForLastRead, scrollStateKey)
|
SaveableCardFeedState(viewModel, accountViewModel, nav, routeForLastRead, scrollStateKey)
|
||||||
|
|
||||||
if (enablePullRefresh) {
|
|
||||||
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ class NotificationViewModel(val account: Account) :
|
||||||
}
|
}
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
|
open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel(), InvalidatableViewModel {
|
||||||
private val _feedContent = MutableStateFlow<CardFeedState>(CardFeedState.Loading)
|
private val _feedContent = MutableStateFlow<CardFeedState>(CardFeedState.Loading)
|
||||||
val feedContent = _feedContent.asStateFlow()
|
val feedContent = _feedContent.asStateFlow()
|
||||||
|
|
||||||
|
@ -358,7 +358,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
|
||||||
private val bundler = BundledUpdate(1000, Dispatchers.IO)
|
private val bundler = BundledUpdate(1000, Dispatchers.IO)
|
||||||
private val bundlerInsert = BundledInsert<Set<Note>>(1000, Dispatchers.IO)
|
private val bundlerInsert = BundledInsert<Set<Note>>(1000, Dispatchers.IO)
|
||||||
|
|
||||||
fun invalidateData(ignoreIfDoing: Boolean = false) {
|
override fun invalidateData(ignoreIfDoing: Boolean) {
|
||||||
bundler.invalidate(ignoreIfDoing) {
|
bundler.invalidate(ignoreIfDoing) {
|
||||||
// adds the time to perform the refresh into this delay
|
// adds the time to perform the refresh into this delay
|
||||||
// holding off new updates in case of heavy refresh routines.
|
// holding off new updates in case of heavy refresh routines.
|
||||||
|
@ -367,9 +367,9 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invalidateDataAndSendToTop() {
|
fun invalidateDataAndSendToTop(ignoreIfDoing: Boolean) {
|
||||||
clear()
|
clear()
|
||||||
bundler.invalidate(false) {
|
bundler.invalidate(ignoreIfDoing) {
|
||||||
// adds the time to perform the refresh into this delay
|
// adds the time to perform the refresh into this delay
|
||||||
// holding off new updates in case of heavy refresh routines.
|
// holding off new updates in case of heavy refresh routines.
|
||||||
val (value, elapsed) =
|
val (value, elapsed) =
|
||||||
|
|
|
@ -21,23 +21,16 @@
|
||||||
package com.vitorpamplona.amethyst.ui.screen
|
package com.vitorpamplona.amethyst.ui.screen
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
import androidx.compose.material3.pullrefresh.PullRefreshIndicator
|
|
||||||
import androidx.compose.material3.pullrefresh.pullRefresh
|
|
||||||
import androidx.compose.material3.pullrefresh.rememberPullRefreshState
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
@ -57,7 +50,7 @@ import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class RelayFeedViewModel : ViewModel() {
|
class RelayFeedViewModel : ViewModel(), InvalidatableViewModel {
|
||||||
val order =
|
val order =
|
||||||
compareByDescending<RelayInfo> { it.lastEvent }
|
compareByDescending<RelayInfo> { it.lastEvent }
|
||||||
.thenByDescending { it.counter }
|
.thenByDescending { it.counter }
|
||||||
|
@ -112,8 +105,8 @@ class RelayFeedViewModel : ViewModel() {
|
||||||
|
|
||||||
private val bundler = BundledUpdate(250, Dispatchers.IO)
|
private val bundler = BundledUpdate(250, Dispatchers.IO)
|
||||||
|
|
||||||
fun invalidateData() {
|
override fun invalidateData(ignoreIfDoing: Boolean) {
|
||||||
bundler.invalidate {
|
bundler.invalidate(ignoreIfDoing) {
|
||||||
// adds the time to perform the refresh into this delay
|
// adds the time to perform the refresh into this delay
|
||||||
// holding off new updates in case of heavy refresh routines.
|
// holding off new updates in case of heavy refresh routines.
|
||||||
refreshSuspended()
|
refreshSuspended()
|
||||||
|
@ -142,22 +135,7 @@ fun RelayFeedView(
|
||||||
NewRelayListView({ wantsToAddRelay = "" }, accountViewModel, wantsToAddRelay, nav = nav)
|
NewRelayListView({ wantsToAddRelay = "" }, accountViewModel, wantsToAddRelay, nav = nav)
|
||||||
}
|
}
|
||||||
|
|
||||||
var refreshing by remember { mutableStateOf(false) }
|
RefresheableBox(viewModel, enablePullRefresh) {
|
||||||
val refresh = {
|
|
||||||
refreshing = true
|
|
||||||
viewModel.refresh()
|
|
||||||
refreshing = false
|
|
||||||
}
|
|
||||||
val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh)
|
|
||||||
|
|
||||||
val modifier =
|
|
||||||
if (enablePullRefresh) {
|
|
||||||
Modifier.fillMaxSize().pullRefresh(pullRefreshState)
|
|
||||||
} else {
|
|
||||||
Modifier.fillMaxSize()
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(modifier) {
|
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
@ -176,9 +154,5 @@ fun RelayFeedView(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enablePullRefresh) {
|
|
||||||
PullRefreshIndicator(refreshing, pullRefreshState, Modifier.align(Alignment.TopCenter))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -554,7 +554,7 @@ fun NoteMaster(
|
||||||
} else if (noteEvent is AppDefinitionEvent) {
|
} else if (noteEvent is AppDefinitionEvent) {
|
||||||
RenderAppDefinition(baseNote, accountViewModel, nav)
|
RenderAppDefinition(baseNote, accountViewModel, nav)
|
||||||
} else if (noteEvent is DraftEvent) {
|
} else if (noteEvent is DraftEvent) {
|
||||||
RenderDraft(baseNote, backgroundColor, accountViewModel, nav)
|
RenderDraft(baseNote, 3, backgroundColor, accountViewModel, nav)
|
||||||
} else if (noteEvent is HighlightEvent) {
|
} else if (noteEvent is HighlightEvent) {
|
||||||
DisplayHighlight(
|
DisplayHighlight(
|
||||||
noteEvent.quote(),
|
noteEvent.quote(),
|
||||||
|
|
|
@ -1323,20 +1323,11 @@ class AccountViewModel(val account: Account, val settings: SettingsState) : View
|
||||||
account.deleteDraft(draftTag)
|
account.deleteDraft(draftTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createTempCachedDraftNote(
|
suspend fun createTempDraftNote(
|
||||||
noteEvent: DraftEvent,
|
noteEvent: DraftEvent,
|
||||||
author: User,
|
onReady: (Note?) -> Unit,
|
||||||
): Note? {
|
|
||||||
return noteEvent.preCachedDraft(account.signer)?.let { createTempDraftNote(it, author) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createTempDraftNote(
|
|
||||||
noteEvent: DraftEvent,
|
|
||||||
author: User,
|
|
||||||
onReady: (Note) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
draftNoteCache.update(noteEvent, onReady)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createTempDraftNote(
|
fun createTempDraftNote(
|
||||||
|
|
|
@ -146,6 +146,7 @@ import com.vitorpamplona.amethyst.ui.theme.EditFieldLeadingIconModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier
|
import com.vitorpamplona.amethyst.ui.theme.EditFieldModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
|
import com.vitorpamplona.amethyst.ui.theme.EditFieldTrailingIconModifier
|
||||||
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier
|
||||||
|
import com.vitorpamplona.amethyst.ui.theme.RowColSpacing
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
import com.vitorpamplona.amethyst.ui.theme.Size34dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
import com.vitorpamplona.amethyst.ui.theme.Size35dp
|
||||||
|
@ -974,21 +975,21 @@ private fun ShortChannelActionOptions(
|
||||||
) {
|
) {
|
||||||
LoadNote(baseNoteHex = channel.idHex, accountViewModel) {
|
LoadNote(baseNoteHex = channel.idHex, accountViewModel) {
|
||||||
it?.let {
|
it?.let {
|
||||||
Spacer(modifier = StdHorzSpacer)
|
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = RowColSpacing) {
|
||||||
LikeReaction(
|
LikeReaction(
|
||||||
baseNote = it,
|
baseNote = it,
|
||||||
grayTint = MaterialTheme.colorScheme.onSurface,
|
grayTint = MaterialTheme.colorScheme.onSurface,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav,
|
nav,
|
||||||
)
|
)
|
||||||
Spacer(modifier = StdHorzSpacer)
|
ZapReaction(
|
||||||
ZapReaction(
|
baseNote = it,
|
||||||
baseNote = it,
|
grayTint = MaterialTheme.colorScheme.onSurface,
|
||||||
grayTint = MaterialTheme.colorScheme.onSurface,
|
accountViewModel = accountViewModel,
|
||||||
accountViewModel = accountViewModel,
|
nav = nav,
|
||||||
nav = nav,
|
)
|
||||||
)
|
Spacer(modifier = StdHorzSpacer)
|
||||||
Spacer(modifier = StdHorzSpacer)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -276,7 +276,7 @@ fun MainScreen(
|
||||||
discoveryChatFeedViewModel.sendToTop()
|
discoveryChatFeedViewModel.sendToTop()
|
||||||
}
|
}
|
||||||
Route.Notification.base -> {
|
Route.Notification.base -> {
|
||||||
notifFeedViewModel.invalidateDataAndSendToTop()
|
notifFeedViewModel.invalidateDataAndSendToTop(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -274,6 +274,7 @@
|
||||||
<string name="report_dialog_title">Blokovat a nahlásit</string>
|
<string name="report_dialog_title">Blokovat a nahlásit</string>
|
||||||
<string name="block_only">Blokovat</string>
|
<string name="block_only">Blokovat</string>
|
||||||
<string name="bookmarks">Záložky</string>
|
<string name="bookmarks">Záložky</string>
|
||||||
|
<string name="drafts">Koncepty</string>
|
||||||
<string name="private_bookmarks">Soukromé záložky</string>
|
<string name="private_bookmarks">Soukromé záložky</string>
|
||||||
<string name="public_bookmarks">Veřejné záložky</string>
|
<string name="public_bookmarks">Veřejné záložky</string>
|
||||||
<string name="add_to_private_bookmarks">Přidat do soukromých záložek</string>
|
<string name="add_to_private_bookmarks">Přidat do soukromých záložek</string>
|
||||||
|
@ -620,6 +621,7 @@
|
||||||
<string name="server_did_not_provide_a_url_after_uploading">Server po nahrání neposkytl URL</string>
|
<string name="server_did_not_provide_a_url_after_uploading">Server po nahrání neposkytl URL</string>
|
||||||
<string name="could_not_download_from_the_server">Nepodařilo se stáhnout nahraná média ze serveru</string>
|
<string name="could_not_download_from_the_server">Nepodařilo se stáhnout nahraná média ze serveru</string>
|
||||||
<string name="could_not_prepare_local_file_to_upload">Nelze připravit místní soubor k nahrání: %1$s</string>
|
<string name="could_not_prepare_local_file_to_upload">Nelze připravit místní soubor k nahrání: %1$s</string>
|
||||||
|
<string name="edit_draft">Upravit koncept</string>
|
||||||
<string name="login_with_qr_code">Přihlášení pomocí QR kódu</string>
|
<string name="login_with_qr_code">Přihlášení pomocí QR kódu</string>
|
||||||
<string name="route">Trasa</string>
|
<string name="route">Trasa</string>
|
||||||
<string name="route_home">Domů</string>
|
<string name="route_home">Domů</string>
|
||||||
|
@ -691,4 +693,10 @@
|
||||||
<string name="accessibility_play_username">Přehrát uživatelské jméno jako audio</string>
|
<string name="accessibility_play_username">Přehrát uživatelské jméno jako audio</string>
|
||||||
<string name="accessibility_scan_qr_code">Skenovat QR kód</string>
|
<string name="accessibility_scan_qr_code">Skenovat QR kód</string>
|
||||||
<string name="accessibility_navigate_to_alby">Přejít na poskytovatele peněženky třetí strany Alby</string>
|
<string name="accessibility_navigate_to_alby">Přejít na poskytovatele peněženky třetí strany Alby</string>
|
||||||
|
<string name="it_s_not_possible_to_reply_to_a_draft_note">Není možné odpovědět na koncept</string>
|
||||||
|
<string name="it_s_not_possible_to_quote_to_a_draft_note">Není možné citovat koncept</string>
|
||||||
|
<string name="it_s_not_possible_to_react_to_a_draft_note">Není možné reagovat na koncept</string>
|
||||||
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">Není možné poslat zap konceptu</string>
|
||||||
|
<string name="draft_note">Koncept</string>
|
||||||
|
<string name="load_from_text">Ze zprávy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -280,6 +280,7 @@ anz der Bedingungen ist erforderlich</string>
|
||||||
<string name="report_dialog_title">Blockieren und melden</string>
|
<string name="report_dialog_title">Blockieren und melden</string>
|
||||||
<string name="block_only">Blockieren</string>
|
<string name="block_only">Blockieren</string>
|
||||||
<string name="bookmarks">Lesezeichen</string>
|
<string name="bookmarks">Lesezeichen</string>
|
||||||
|
<string name="drafts">Entwürfe</string>
|
||||||
<string name="private_bookmarks">Private Lesezeichen</string>
|
<string name="private_bookmarks">Private Lesezeichen</string>
|
||||||
<string name="public_bookmarks">Öffentliche Lesezeichen</string>
|
<string name="public_bookmarks">Öffentliche Lesezeichen</string>
|
||||||
<string name="add_to_private_bookmarks">Zu den privaten Lesezeichen hinzufügen</string>
|
<string name="add_to_private_bookmarks">Zu den privaten Lesezeichen hinzufügen</string>
|
||||||
|
@ -625,6 +626,7 @@ anz der Bedingungen ist erforderlich</string>
|
||||||
<string name="server_did_not_provide_a_url_after_uploading">Der Server hat nach dem Hochladen keine URL angegeben</string>
|
<string name="server_did_not_provide_a_url_after_uploading">Der Server hat nach dem Hochladen keine URL angegeben</string>
|
||||||
<string name="could_not_download_from_the_server">Hochgeladene Medien konnten nicht vom Server heruntergeladen werden</string>
|
<string name="could_not_download_from_the_server">Hochgeladene Medien konnten nicht vom Server heruntergeladen werden</string>
|
||||||
<string name="could_not_prepare_local_file_to_upload">Lokale Datei konnte nicht zum Hochladen vorbereitet werden: %1$s</string>
|
<string name="could_not_prepare_local_file_to_upload">Lokale Datei konnte nicht zum Hochladen vorbereitet werden: %1$s</string>
|
||||||
|
<string name="edit_draft">Entwurf bearbeiten</string>
|
||||||
<string name="login_with_qr_code">Einloggen mit QR-Code</string>
|
<string name="login_with_qr_code">Einloggen mit QR-Code</string>
|
||||||
<string name="route">Route</string>
|
<string name="route">Route</string>
|
||||||
<string name="route_home">Startseite</string>
|
<string name="route_home">Startseite</string>
|
||||||
|
@ -696,4 +698,10 @@ anz der Bedingungen ist erforderlich</string>
|
||||||
<string name="accessibility_play_username">Benutzernamen als Audio abspielen</string>
|
<string name="accessibility_play_username">Benutzernamen als Audio abspielen</string>
|
||||||
<string name="accessibility_scan_qr_code">QR-Code scannen</string>
|
<string name="accessibility_scan_qr_code">QR-Code scannen</string>
|
||||||
<string name="accessibility_navigate_to_alby">Navigieren Sie zum Drittanbieter-Wallet-Anbieter Alby</string>
|
<string name="accessibility_navigate_to_alby">Navigieren Sie zum Drittanbieter-Wallet-Anbieter Alby</string>
|
||||||
|
<string name="it_s_not_possible_to_reply_to_a_draft_note">Es ist nicht möglich, auf einen Entwurf zu antworten</string>
|
||||||
|
<string name="it_s_not_possible_to_quote_to_a_draft_note">Es ist nicht möglich, einen Entwurf zu zitieren</string>
|
||||||
|
<string name="it_s_not_possible_to_react_to_a_draft_note">Es ist nicht möglich, auf einen Entwurf zu reagieren</string>
|
||||||
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">Es ist nicht möglich, zap Zahlung an einen Entwurf senden</string>
|
||||||
|
<string name="draft_note">Entwurf</string>
|
||||||
|
<string name="load_from_text">Aus Nachricht laden</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -276,6 +276,7 @@
|
||||||
<string name="report_dialog_title">Bloquear y reportar</string>
|
<string name="report_dialog_title">Bloquear y reportar</string>
|
||||||
<string name="block_only">Bloquear</string>
|
<string name="block_only">Bloquear</string>
|
||||||
<string name="bookmarks">Marcadores</string>
|
<string name="bookmarks">Marcadores</string>
|
||||||
|
<string name="drafts">Borradores</string>
|
||||||
<string name="private_bookmarks">Marcadores privados</string>
|
<string name="private_bookmarks">Marcadores privados</string>
|
||||||
<string name="public_bookmarks">Marcadores públicos</string>
|
<string name="public_bookmarks">Marcadores públicos</string>
|
||||||
<string name="add_to_private_bookmarks">Agregar a marcadores privados</string>
|
<string name="add_to_private_bookmarks">Agregar a marcadores privados</string>
|
||||||
|
|
|
@ -276,6 +276,7 @@
|
||||||
<string name="report_dialog_title">Bloquear y reportar</string>
|
<string name="report_dialog_title">Bloquear y reportar</string>
|
||||||
<string name="block_only">Bloquear</string>
|
<string name="block_only">Bloquear</string>
|
||||||
<string name="bookmarks">Marcadores</string>
|
<string name="bookmarks">Marcadores</string>
|
||||||
|
<string name="drafts">Borradores</string>
|
||||||
<string name="private_bookmarks">Marcadores privados</string>
|
<string name="private_bookmarks">Marcadores privados</string>
|
||||||
<string name="public_bookmarks">Marcadores públicos</string>
|
<string name="public_bookmarks">Marcadores públicos</string>
|
||||||
<string name="add_to_private_bookmarks">Agregar a marcadores privados</string>
|
<string name="add_to_private_bookmarks">Agregar a marcadores privados</string>
|
||||||
|
|
|
@ -276,6 +276,7 @@
|
||||||
<string name="report_dialog_title">Bloquer et Signaler</string>
|
<string name="report_dialog_title">Bloquer et Signaler</string>
|
||||||
<string name="block_only">Bloquer</string>
|
<string name="block_only">Bloquer</string>
|
||||||
<string name="bookmarks">Favoris</string>
|
<string name="bookmarks">Favoris</string>
|
||||||
|
<string name="drafts">Brouillons</string>
|
||||||
<string name="private_bookmarks">Favoris Privés</string>
|
<string name="private_bookmarks">Favoris Privés</string>
|
||||||
<string name="public_bookmarks">Favoris Publics</string>
|
<string name="public_bookmarks">Favoris Publics</string>
|
||||||
<string name="add_to_private_bookmarks">Ajouter aux Favoris Privés</string>
|
<string name="add_to_private_bookmarks">Ajouter aux Favoris Privés</string>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<string name="point_to_the_qr_code">Aponte para o código QR</string>
|
<string name="point_to_the_qr_code">Aponte para o QR code</string>
|
||||||
<string name="show_qr">Mostrar QR</string>
|
<string name="show_qr">Mostrar QR</string>
|
||||||
<string name="profile_image">Imagem de perfil</string>
|
<string name="profile_image">Imagem de perfil</string>
|
||||||
<string name="your_profile_image">Sua Foto de Perfil</string>
|
<string name="your_profile_image">Sua Foto de Perfil</string>
|
||||||
|
@ -274,6 +274,7 @@
|
||||||
<string name="report_dialog_title">Bloquear e Denunciar</string>
|
<string name="report_dialog_title">Bloquear e Denunciar</string>
|
||||||
<string name="block_only">Bloquear</string>
|
<string name="block_only">Bloquear</string>
|
||||||
<string name="bookmarks">Itens Salvos</string>
|
<string name="bookmarks">Itens Salvos</string>
|
||||||
|
<string name="drafts">Rascunhos</string>
|
||||||
<string name="private_bookmarks">Itens Salvos Privados</string>
|
<string name="private_bookmarks">Itens Salvos Privados</string>
|
||||||
<string name="public_bookmarks">Itens Salvos Públicos</string>
|
<string name="public_bookmarks">Itens Salvos Públicos</string>
|
||||||
<string name="add_to_private_bookmarks">Adicionar aos Itens Salvos Privados</string>
|
<string name="add_to_private_bookmarks">Adicionar aos Itens Salvos Privados</string>
|
||||||
|
@ -620,6 +621,7 @@
|
||||||
<string name="server_did_not_provide_a_url_after_uploading">O servidor não forneceu uma URL após o upload</string>
|
<string name="server_did_not_provide_a_url_after_uploading">O servidor não forneceu uma URL após o upload</string>
|
||||||
<string name="could_not_download_from_the_server">Não foi possível baixar o arquivo de mídia carregado do servidor</string>
|
<string name="could_not_download_from_the_server">Não foi possível baixar o arquivo de mídia carregado do servidor</string>
|
||||||
<string name="could_not_prepare_local_file_to_upload">Não foi possível preparar o arquivo local para enviar: %1$s</string>
|
<string name="could_not_prepare_local_file_to_upload">Não foi possível preparar o arquivo local para enviar: %1$s</string>
|
||||||
|
<string name="edit_draft">Editar rascunho</string>
|
||||||
<string name="login_with_qr_code">Entrar com Código QR</string>
|
<string name="login_with_qr_code">Entrar com Código QR</string>
|
||||||
<string name="route">Rota</string>
|
<string name="route">Rota</string>
|
||||||
<string name="route_home">Início</string>
|
<string name="route_home">Início</string>
|
||||||
|
@ -691,4 +693,10 @@
|
||||||
<string name="accessibility_play_username">Reproduzir nome de usuário como áudio</string>
|
<string name="accessibility_play_username">Reproduzir nome de usuário como áudio</string>
|
||||||
<string name="accessibility_scan_qr_code">Escanear código QR</string>
|
<string name="accessibility_scan_qr_code">Escanear código QR</string>
|
||||||
<string name="accessibility_navigate_to_alby">Navegar para o provedor de carteira de terceiros Alby</string>
|
<string name="accessibility_navigate_to_alby">Navegar para o provedor de carteira de terceiros Alby</string>
|
||||||
|
<string name="it_s_not_possible_to_reply_to_a_draft_note">Não é possível responder uma nota em rascunho</string>
|
||||||
|
<string name="it_s_not_possible_to_quote_to_a_draft_note">Não é possível citar uma nota em rascunho</string>
|
||||||
|
<string name="it_s_not_possible_to_react_to_a_draft_note">Não é possível reagir uma nota em rascunho</string>
|
||||||
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">Não é possível fazer um zap em uma nota em rascunho</string>
|
||||||
|
<string name="draft_note">Nota de rascunho</string>
|
||||||
|
<string name="load_from_text">Carregar do texto</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -274,6 +274,7 @@
|
||||||
<string name="report_dialog_title">Blockera och rapportera</string>
|
<string name="report_dialog_title">Blockera och rapportera</string>
|
||||||
<string name="block_only">Blockera</string>
|
<string name="block_only">Blockera</string>
|
||||||
<string name="bookmarks">Bokmärken</string>
|
<string name="bookmarks">Bokmärken</string>
|
||||||
|
<string name="drafts">Utkast</string>
|
||||||
<string name="private_bookmarks">Privata Bokmärken</string>
|
<string name="private_bookmarks">Privata Bokmärken</string>
|
||||||
<string name="public_bookmarks">Publika Bokmärken</string>
|
<string name="public_bookmarks">Publika Bokmärken</string>
|
||||||
<string name="add_to_private_bookmarks">Lägg till i Privata Bokmärken</string>
|
<string name="add_to_private_bookmarks">Lägg till i Privata Bokmärken</string>
|
||||||
|
@ -619,6 +620,7 @@
|
||||||
<string name="server_did_not_provide_a_url_after_uploading">Servern gav inte en URL efter uppladdning</string>
|
<string name="server_did_not_provide_a_url_after_uploading">Servern gav inte en URL efter uppladdning</string>
|
||||||
<string name="could_not_download_from_the_server">Kunde inte ladda ner uppladdade medier från servern</string>
|
<string name="could_not_download_from_the_server">Kunde inte ladda ner uppladdade medier från servern</string>
|
||||||
<string name="could_not_prepare_local_file_to_upload">Kunde inte förbereda lokal fil att ladda upp: %1$s</string>
|
<string name="could_not_prepare_local_file_to_upload">Kunde inte förbereda lokal fil att ladda upp: %1$s</string>
|
||||||
|
<string name="edit_draft">Redigera utkast</string>
|
||||||
<string name="login_with_qr_code">Logga in med QR-kod</string>
|
<string name="login_with_qr_code">Logga in med QR-kod</string>
|
||||||
<string name="route">Rutt</string>
|
<string name="route">Rutt</string>
|
||||||
<string name="route_home">Hem</string>
|
<string name="route_home">Hem</string>
|
||||||
|
@ -690,4 +692,10 @@
|
||||||
<string name="accessibility_play_username">Spela upp användarnamn som ljud</string>
|
<string name="accessibility_play_username">Spela upp användarnamn som ljud</string>
|
||||||
<string name="accessibility_scan_qr_code">Skanna QR-kod</string>
|
<string name="accessibility_scan_qr_code">Skanna QR-kod</string>
|
||||||
<string name="accessibility_navigate_to_alby">Navigera till tredjeparts plånboksleverantören Alby</string>
|
<string name="accessibility_navigate_to_alby">Navigera till tredjeparts plånboksleverantören Alby</string>
|
||||||
|
<string name="it_s_not_possible_to_reply_to_a_draft_note">Det går inte att svara på ett utkast</string>
|
||||||
|
<string name="it_s_not_possible_to_quote_to_a_draft_note">Det går inte att citera ett utkast</string>
|
||||||
|
<string name="it_s_not_possible_to_react_to_a_draft_note">Det går inte att reagera på ett utkast</string>
|
||||||
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">Det går inte att zappa ett utkast</string>
|
||||||
|
<string name="draft_note">Utkast</string>
|
||||||
|
<string name="load_from_text">Från meddelande</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -275,6 +275,7 @@
|
||||||
<string name="report_dialog_title">บล๊อกและรายงาน</string>
|
<string name="report_dialog_title">บล๊อกและรายงาน</string>
|
||||||
<string name="block_only">บล๊อก</string>
|
<string name="block_only">บล๊อก</string>
|
||||||
<string name="bookmarks">บุ๊คมาร์ค</string>
|
<string name="bookmarks">บุ๊คมาร์ค</string>
|
||||||
|
<string name="drafts">ฉบับร่าง</string>
|
||||||
<string name="private_bookmarks">บุ๊คมาร์คส่วนตัว</string>
|
<string name="private_bookmarks">บุ๊คมาร์คส่วนตัว</string>
|
||||||
<string name="public_bookmarks">บุ๊คมาร์คสาธรณะ</string>
|
<string name="public_bookmarks">บุ๊คมาร์คสาธรณะ</string>
|
||||||
<string name="add_to_private_bookmarks">เพิ่มไปยังบุ๊คมาร์คส่วนตัว</string>
|
<string name="add_to_private_bookmarks">เพิ่มไปยังบุ๊คมาร์คส่วนตัว</string>
|
||||||
|
@ -440,6 +441,8 @@
|
||||||
<string name="connectivity_type_always">ตลอดเวลา</string>
|
<string name="connectivity_type_always">ตลอดเวลา</string>
|
||||||
<string name="connectivity_type_wifi_only">Wifi เท่านั้น</string>
|
<string name="connectivity_type_wifi_only">Wifi เท่านั้น</string>
|
||||||
<string name="connectivity_type_never">ไม่ต้องแสดง</string>
|
<string name="connectivity_type_never">ไม่ต้องแสดง</string>
|
||||||
|
<string name="ui_feature_set_type_complete">เสร็จสมบูรณ์</string>
|
||||||
|
<string name="ui_feature_set_type_simplified">Simplified</string>
|
||||||
<string name="system">ระบบ(ค่าพื้นฐาน)</string>
|
<string name="system">ระบบ(ค่าพื้นฐาน)</string>
|
||||||
<string name="light">สว่าง</string>
|
<string name="light">สว่าง</string>
|
||||||
<string name="dark">มืด</string>
|
<string name="dark">มืด</string>
|
||||||
|
@ -451,6 +454,8 @@
|
||||||
<string name="automatically_show_url_preview">การแสดงตัวอย่าง URL</string>
|
<string name="automatically_show_url_preview">การแสดงตัวอย่าง URL</string>
|
||||||
<string name="automatically_hide_nav_bars">การไถฟีดแบบลื่นไหล</string>
|
<string name="automatically_hide_nav_bars">การไถฟีดแบบลื่นไหล</string>
|
||||||
<string name="automatically_hide_nav_bars_description">ซ่อนแถบเมนูขณะเลื่อนฟีด</string>
|
<string name="automatically_hide_nav_bars_description">ซ่อนแถบเมนูขณะเลื่อนฟีด</string>
|
||||||
|
<string name="ui_style">UI Mode</string>
|
||||||
|
<string name="ui_style_description">เลือกรูปแบบการโพสต์</string>
|
||||||
<string name="load_image">โหลดรูปภาพ</string>
|
<string name="load_image">โหลดรูปภาพ</string>
|
||||||
<string name="spamming_users">แสปม</string>
|
<string name="spamming_users">แสปม</string>
|
||||||
<string name="muted_button">ปิดการมองเห็น คลิกเพื่อปลดออก</string>
|
<string name="muted_button">ปิดการมองเห็น คลิกเพื่อปลดออก</string>
|
||||||
|
@ -615,6 +620,7 @@
|
||||||
<string name="server_did_not_provide_a_url_after_uploading">เซอเวอร์ไม่ตอบสนองหลังจากอัพโหลดแล้ว</string>
|
<string name="server_did_not_provide_a_url_after_uploading">เซอเวอร์ไม่ตอบสนองหลังจากอัพโหลดแล้ว</string>
|
||||||
<string name="could_not_download_from_the_server">ไม่สามารถโหลดข้อมูลสื่อได้</string>
|
<string name="could_not_download_from_the_server">ไม่สามารถโหลดข้อมูลสื่อได้</string>
|
||||||
<string name="could_not_prepare_local_file_to_upload">ไม่สามารถเตรียมข้อมูลสำหรับอัพโหลด : %1$s</string>
|
<string name="could_not_prepare_local_file_to_upload">ไม่สามารถเตรียมข้อมูลสำหรับอัพโหลด : %1$s</string>
|
||||||
|
<string name="edit_draft">แก้ไขฉบับร่าง</string>
|
||||||
<string name="login_with_qr_code">เข้าสู่ระบบด้วย qr code</string>
|
<string name="login_with_qr_code">เข้าสู่ระบบด้วย qr code</string>
|
||||||
<string name="route">เส้นทาง</string>
|
<string name="route">เส้นทาง</string>
|
||||||
<string name="route_home">หน้าแรก</string>
|
<string name="route_home">หน้าแรก</string>
|
||||||
|
@ -638,11 +644,58 @@
|
||||||
<string name="relay_info">รีเลย์ %1$s</string>
|
<string name="relay_info">รีเลย์ %1$s</string>
|
||||||
<string name="expand_relay_list">แสดงรายการรีเลย์</string>
|
<string name="expand_relay_list">แสดงรายการรีเลย์</string>
|
||||||
<string name="note_options">ตัวเลือกเพิ่มเติม</string>
|
<string name="note_options">ตัวเลือกเพิ่มเติม</string>
|
||||||
|
<string name="relay_list_selector">ตัวเลือกรายการรีเลย์</string>
|
||||||
<string name="poll">โพลล์</string>
|
<string name="poll">โพลล์</string>
|
||||||
|
<string name="disable_poll">ยกเลิกโพลล์</string>
|
||||||
|
<string name="add_bitcoin_invoice">Bitcoin Invoice</string>
|
||||||
|
<string name="cancel_bitcoin_invoice">ยกเลิก Bitcoin Invoice</string>
|
||||||
|
<string name="cancel_classifieds">ยกเลิกการขายสิ่งนี้</string>
|
||||||
|
<string name="add_zapraiser">ระดมทุน</string>
|
||||||
|
<string name="cancel_zapraiser">ยกเลิกการระดมทุน</string>
|
||||||
|
<string name="add_location">ตำแหน่ง</string>
|
||||||
|
<string name="remove_location">ลบตำแหน่งออก</string>
|
||||||
|
<string name="add_zap_split">Zap splits</string>
|
||||||
|
<string name="cancel_zap_split">ยกเลิก Zap split</string>
|
||||||
|
<string name="add_content_warning">เพิ่มคําเตือนเนื้อหา</string>
|
||||||
|
<string name="remove_content_warning">ลบคําเตือนเนื้อหา</string>
|
||||||
<string name="show_npub_as_a_qr_code">แสดง npub เป็น qr code</string>
|
<string name="show_npub_as_a_qr_code">แสดง npub เป็น qr code</string>
|
||||||
|
<string name="invalid_nip19_uri">ที่อยู่ไม่ถูกต้อง</string>
|
||||||
|
<string name="invalid_nip19_uri_description">Amethyst ได้รับ URI แต่ URI นั้นไม่ถูกต้อง: %1$s</string>
|
||||||
|
<string name="zap_the_devs_title">zap ให้นักพัฒนา</string>
|
||||||
|
<string name="zap_the_devs_description">การบริจาคของคุณช่วยให้เราสร้างความแตกต่าง ทุก sat มีค่า!</string>
|
||||||
|
<string name="donate_now">บริจาค</string>
|
||||||
|
<string name="brought_to_you_by">มาถึงคุณโดย:</string>
|
||||||
|
<string name="this_version_brought_to_you_by">เวอร์ชันนี้นำเสนอโดย:</string>
|
||||||
|
<string name="version_name">เวอร์ชัน %1$s</string>
|
||||||
<string name="thank_you">ขอบคุณ!</string>
|
<string name="thank_you">ขอบคุณ!</string>
|
||||||
|
<string name="max_limit">Max Limit</string>
|
||||||
|
<string name="restricted_writes">การเขียนที่ถูกจำกัด</string>
|
||||||
|
<string name="forked_from">คัดลอกมาจาก</string>
|
||||||
|
<string name="forked_tag">คัดลอก</string>
|
||||||
|
<string name="git_repository">Git Repository: %1$s</string>
|
||||||
|
<string name="git_web_address">เว็บไซน์</string>
|
||||||
|
<string name="git_clone_address">สำเนา:</string>
|
||||||
|
<string name="existed_since">OTS: %1$s</string>
|
||||||
|
<string name="ots_info_title">Timestamp ถูกยืนยัน</string>
|
||||||
|
<string name="ots_info_description">มีหลักฐานว่าโพสต์นี้มีการลงนามก่อน %1$s หลักฐานถูกประทับตราใน Bitcoin blockchain ณ วันและเวลาดังกล่าว</string>
|
||||||
<string name="edit_post">แก้ไขโพสต์</string>
|
<string name="edit_post">แก้ไขโพสต์</string>
|
||||||
|
<string name="proposal_to_edit">เสนอให้ปรับปรุงการโพสต์</string>
|
||||||
|
<string name="message_to_author">สรุปการเปลี่ยนแปลง</string>
|
||||||
|
<string name="message_to_author_placeholder">แก้ไขด่วน</string>
|
||||||
|
<string name="accept_the_suggestion">ยอมรับการแนะนำนี้</string>
|
||||||
<string name="accessibility_download_for_offline">ดาวน์โหลด</string>
|
<string name="accessibility_download_for_offline">ดาวน์โหลด</string>
|
||||||
|
<string name="accessibility_lyrics_on">Lyrics on</string>
|
||||||
|
<string name="accessibility_lyrics_off">Lyrics off</string>
|
||||||
|
<string name="accessibility_turn_on_sealed_message">ปิดผนึกข้อความแล้ว คลิกเพื่อเปิดข้อความ</string>
|
||||||
|
<string name="accessibility_turn_off_sealed_message">เปิดผนึกข้อความไว้ คลิกเพื่อปิด</string>
|
||||||
<string name="accessibility_send">ส่ง</string>
|
<string name="accessibility_send">ส่ง</string>
|
||||||
|
<string name="accessibility_play_username">อ่านชื่อผู้ใช้เป็นเสียง</string>
|
||||||
<string name="accessibility_scan_qr_code">สแกนคิวอาร์โค้ด</string>
|
<string name="accessibility_scan_qr_code">สแกนคิวอาร์โค้ด</string>
|
||||||
|
<string name="accessibility_navigate_to_alby">นำทางไปยังผู้ให้บริการกระเป๋าเงินบุคคลที่สาม Alby</string>
|
||||||
|
<string name="it_s_not_possible_to_reply_to_a_draft_note">ไม่สามารถตอบกลับโน้ตฉบับร่างได้</string>
|
||||||
|
<string name="it_s_not_possible_to_quote_to_a_draft_note">ไม่สามารถโควทโน้ตฉบับร่างได้</string>
|
||||||
|
<string name="it_s_not_possible_to_react_to_a_draft_note">ไม่สามารถกดรีแอคโน้ตฉบับร่างได้</string>
|
||||||
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">ไม่สามารถ zap โน้ตฉบับร่างได้</string>
|
||||||
|
<string name="draft_note">โน๊ตฉบับร่าง</string>
|
||||||
|
<string name="load_from_text">From Msg</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -827,4 +827,6 @@
|
||||||
<string name="it_s_not_possible_to_react_to_a_draft_note">It\'s not possible to react a draft note</string>
|
<string name="it_s_not_possible_to_react_to_a_draft_note">It\'s not possible to react a draft note</string>
|
||||||
<string name="it_s_not_possible_to_zap_to_a_draft_note">It\'s not possible to zap a draft note</string>
|
<string name="it_s_not_possible_to_zap_to_a_draft_note">It\'s not possible to zap a draft note</string>
|
||||||
<string name="draft_note">Draft Note</string>
|
<string name="draft_note">Draft Note</string>
|
||||||
|
|
||||||
|
<string name="load_from_text">From Msg</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2024 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.amethyst.commons.data
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.encoders.HexKey
|
||||||
|
import com.vitorpamplona.quartz.events.AddressableEvent
|
||||||
|
import com.vitorpamplona.quartz.events.DeletionEvent
|
||||||
|
import com.vitorpamplona.quartz.events.Event
|
||||||
|
|
||||||
|
class DeletionIndex {
|
||||||
|
data class DeletionRequest(val reference: String, val publicKey: HexKey) : Comparable<DeletionRequest> {
|
||||||
|
override fun compareTo(other: DeletionRequest): Int {
|
||||||
|
val compared = reference.compareTo(other.reference)
|
||||||
|
|
||||||
|
return if (compared == 0) {
|
||||||
|
publicKey.compareTo(publicKey)
|
||||||
|
} else {
|
||||||
|
compared
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stores a set of id OR atags (kind:pubkey:dtag) by pubkey with the created at of the deletion event.
|
||||||
|
// Anything newer than the date should not be deleted.
|
||||||
|
private val deletedReferencesBefore = LargeCache<DeletionRequest, Long>()
|
||||||
|
|
||||||
|
fun add(event: DeletionEvent): Boolean {
|
||||||
|
var atLeastOne = false
|
||||||
|
|
||||||
|
event.tags.forEach {
|
||||||
|
if (it.size > 1 && (it[0] == "a" || it[0] == "e")) {
|
||||||
|
if (add(it[1], event.pubKey, event.createdAt)) {
|
||||||
|
atLeastOne = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return atLeastOne
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun add(
|
||||||
|
ref: String,
|
||||||
|
byPubKey: HexKey,
|
||||||
|
createdAt: Long,
|
||||||
|
): Boolean {
|
||||||
|
val key = DeletionRequest(ref, byPubKey)
|
||||||
|
val previousDeletionTime = deletedReferencesBefore.get(key)
|
||||||
|
|
||||||
|
if (previousDeletionTime == null || createdAt > previousDeletionTime) {
|
||||||
|
deletedReferencesBefore.put(key, createdAt)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hasBeenDeleted(event: Event): Boolean {
|
||||||
|
val key = DeletionRequest(event.id, event.pubKey)
|
||||||
|
if (hasBeenDeleted(key)) return true
|
||||||
|
|
||||||
|
if (event is AddressableEvent) {
|
||||||
|
if (hasBeenDeleted(DeletionRequest(event.addressTag(), event.pubKey), event.createdAt)) return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun hasBeenDeleted(key: DeletionRequest) = deletedReferencesBefore.containsKey(key)
|
||||||
|
|
||||||
|
private fun hasBeenDeleted(
|
||||||
|
key: DeletionRequest,
|
||||||
|
createdAt: Long,
|
||||||
|
): Boolean {
|
||||||
|
val deletionTime = deletedReferencesBefore.get(key)
|
||||||
|
return deletionTime != null && createdAt <= deletionTime
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ import java.util.concurrent.ConcurrentSkipListMap
|
||||||
import java.util.function.BiConsumer
|
import java.util.function.BiConsumer
|
||||||
|
|
||||||
class LargeCache<K, V> {
|
class LargeCache<K, V> {
|
||||||
val cache = ConcurrentSkipListMap<K, V>()
|
private val cache = ConcurrentSkipListMap<K, V>()
|
||||||
|
|
||||||
fun get(key: K) = cache.get(key)
|
fun get(key: K) = cache.get(key)
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ import androidx.compose.runtime.Immutable
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val relay: String?) {
|
data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val relay: String?) {
|
||||||
fun toTag() = "$kind:$pubKeyHex:$dTag"
|
fun toTag() = assembleATag(kind, pubKeyHex, dTag)
|
||||||
|
|
||||||
fun toNAddr(): String {
|
fun toNAddr(): String {
|
||||||
return TlvBuilder()
|
return TlvBuilder()
|
||||||
|
@ -40,6 +40,12 @@ data class ATag(val kind: Int, val pubKeyHex: String, val dTag: String, val rela
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
fun assembleATag(
|
||||||
|
kind: Int,
|
||||||
|
pubKeyHex: String,
|
||||||
|
dTag: String,
|
||||||
|
) = "$kind:$pubKeyHex:$dTag"
|
||||||
|
|
||||||
fun isATag(key: String): Boolean {
|
fun isATag(key: String): Boolean {
|
||||||
return key.startsWith("naddr1") || key.contains(":")
|
return key.startsWith("naddr1") || key.contains(":")
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ class DeletionEvent(
|
||||||
|
|
||||||
fun deleteAddresses() = taggedAddresses()
|
fun deleteAddresses() = taggedAddresses()
|
||||||
|
|
||||||
|
fun deleteAddressTags() = tags.mapNotNull { if (it.size > 1 && it[0] == "a") it[1] else null }
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val KIND = 5
|
const val KIND = 5
|
||||||
const val ALT = "Deletion event"
|
const val ALT = "Deletion event"
|
||||||
|
|
|
@ -78,7 +78,13 @@ class DraftEvent(
|
||||||
onReady: (Event) -> Unit,
|
onReady: (Event) -> Unit,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
plainContent(signer) { onReady(fromJson(it)) }
|
plainContent(signer) {
|
||||||
|
try {
|
||||||
|
onReady(fromJson(it))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Log.e("UnwrapError", "Couldn't Decrypt the content", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// Log.e("UnwrapError", "Couldn't Decrypt the content", e)
|
// Log.e("UnwrapError", "Couldn't Decrypt the content", e)
|
||||||
}
|
}
|
||||||
|
@ -109,7 +115,7 @@ class DraftEvent(
|
||||||
pubKey: HexKey,
|
pubKey: HexKey,
|
||||||
dTag: String,
|
dTag: String,
|
||||||
): String {
|
): String {
|
||||||
return ATag(KIND, pubKey, dTag, null).toTag()
|
return ATag.assembleATag(KIND, pubKey, dTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
|
|
|
@ -514,6 +514,8 @@ interface AddressableEvent {
|
||||||
fun dTag(): String
|
fun dTag(): String
|
||||||
|
|
||||||
fun address(): ATag
|
fun address(): ATag
|
||||||
|
|
||||||
|
fun addressTag(): String
|
||||||
}
|
}
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
@ -529,6 +531,11 @@ open class BaseAddressableEvent(
|
||||||
override fun dTag() = tags.firstOrNull { it.size > 1 && it[0] == "d" }?.get(1) ?: ""
|
override fun dTag() = tags.firstOrNull { it.size > 1 && it[0] == "d" }?.get(1) ?: ""
|
||||||
|
|
||||||
override fun address() = ATag(kind, pubKey, dTag(), null)
|
override fun address() = ATag(kind, pubKey, dTag(), null)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the tag in a memory effecient way (without creating the ATag class
|
||||||
|
*/
|
||||||
|
override fun addressTag() = ATag.assembleATag(kind, pubKey, dTag())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String.bytesUsedInMemory(): Int {
|
fun String.bytesUsedInMemory(): Int {
|
||||||
|
|
|
@ -44,6 +44,13 @@ class GiftWrapEvent(
|
||||||
return cachedInnerEvent[signer.pubKey]
|
return cachedInnerEvent[signer.pubKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addToCache(
|
||||||
|
pubKey: HexKey,
|
||||||
|
gift: Event,
|
||||||
|
) {
|
||||||
|
cachedInnerEvent = cachedInnerEvent + Pair(pubKey, gift)
|
||||||
|
}
|
||||||
|
|
||||||
fun cachedGift(
|
fun cachedGift(
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
onReady: (Event) -> Unit,
|
onReady: (Event) -> Unit,
|
||||||
|
@ -56,7 +63,7 @@ class GiftWrapEvent(
|
||||||
if (gift is WrappedEvent) {
|
if (gift is WrappedEvent) {
|
||||||
gift.host = this
|
gift.host = this
|
||||||
}
|
}
|
||||||
cachedInnerEvent = cachedInnerEvent + Pair(signer.pubKey, gift)
|
addToCache(signer.pubKey, gift)
|
||||||
|
|
||||||
onReady(gift)
|
onReady(gift)
|
||||||
}
|
}
|
||||||
|
@ -98,8 +105,8 @@ class GiftWrapEvent(
|
||||||
val serializedContent = toJson(event)
|
val serializedContent = toJson(event)
|
||||||
val tags = arrayOf(arrayOf("p", recipientPubKey))
|
val tags = arrayOf(arrayOf("p", recipientPubKey))
|
||||||
|
|
||||||
signer.nip44Encrypt(serializedContent, recipientPubKey) {
|
signer.nip44Encrypt(serializedContent, recipientPubKey) { content ->
|
||||||
signer.sign(createdAt, KIND, tags, it, onReady)
|
signer.sign(createdAt, KIND, tags, content, onReady)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,8 @@ class LongTextNoteEvent(
|
||||||
|
|
||||||
override fun address() = ATag(kind, pubKey, dTag(), null)
|
override fun address() = ATag(kind, pubKey, dTag(), null)
|
||||||
|
|
||||||
|
override fun addressTag() = ATag.assembleATag(kind, pubKey, dTag())
|
||||||
|
|
||||||
fun topics() = hashtags()
|
fun topics() = hashtags()
|
||||||
|
|
||||||
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)
|
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)
|
||||||
|
|
|
@ -45,6 +45,13 @@ class SealedGossipEvent(
|
||||||
return cachedInnerEvent[signer.pubKey]
|
return cachedInnerEvent[signer.pubKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addToCache(
|
||||||
|
pubKey: HexKey,
|
||||||
|
gift: Event,
|
||||||
|
) {
|
||||||
|
cachedInnerEvent = cachedInnerEvent + Pair(pubKey, gift)
|
||||||
|
}
|
||||||
|
|
||||||
fun cachedGossip(
|
fun cachedGossip(
|
||||||
signer: NostrSigner,
|
signer: NostrSigner,
|
||||||
onReady: (Event) -> Unit,
|
onReady: (Event) -> Unit,
|
||||||
|
@ -59,8 +66,8 @@ class SealedGossipEvent(
|
||||||
if (event is WrappedEvent) {
|
if (event is WrappedEvent) {
|
||||||
event.host = host ?: this
|
event.host = host ?: this
|
||||||
}
|
}
|
||||||
|
addToCache(signer.pubKey, event)
|
||||||
|
|
||||||
cachedInnerEvent = cachedInnerEvent + Pair(signer.pubKey, event)
|
|
||||||
onReady(event)
|
onReady(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,8 @@ class WikiNoteEvent(
|
||||||
|
|
||||||
override fun address() = ATag(kind, pubKey, dTag(), null)
|
override fun address() = ATag(kind, pubKey, dTag(), null)
|
||||||
|
|
||||||
|
override fun addressTag() = ATag.assembleATag(kind, pubKey, dTag())
|
||||||
|
|
||||||
fun topics() = hashtags()
|
fun topics() = hashtags()
|
||||||
|
|
||||||
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)
|
fun title() = tags.firstOrNull { it.size > 1 && it[0] == "title" }?.get(1)
|
||||||
|
|
Ładowanie…
Reference in New Issue