New thread ordering scheme:
1. Thread author, ascending order by date. 2. My replies, descending by date. 3. My follow's replies, descending by date. 4. Everybody else, descending by date.
This commit is contained in:
@@ -136,26 +136,60 @@ open class Note(val idHex: String) {
|
|||||||
.format(DateTimeFormatter.ofPattern("uuuu-MM-dd-HH:mm:ss"))
|
.format(DateTimeFormatter.ofPattern("uuuu-MM-dd-HH:mm:ss"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class LevelSignature(val signature: String, val createdAt: Long?, val author: User?)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method caches signatures during each execution to avoid recalculation in longer threads
|
* This method caches signatures during each execution to avoid recalculation in longer threads
|
||||||
*/
|
*/
|
||||||
fun replyLevelSignature(
|
fun replyLevelSignature(
|
||||||
eventsToConsider: Set<Note>,
|
eventsToConsider: Set<Note>,
|
||||||
cachedSignatures: MutableMap<Note, String>
|
cachedSignatures: MutableMap<Note, LevelSignature>,
|
||||||
): String {
|
account: User,
|
||||||
|
accountFollowingSet: Set<String>,
|
||||||
|
now: Long
|
||||||
|
): LevelSignature {
|
||||||
val replyTo = replyTo
|
val replyTo = replyTo
|
||||||
if (event is RepostEvent || event is GenericRepostEvent || replyTo == null || replyTo.isEmpty()) {
|
if (event is RepostEvent || event is GenericRepostEvent || replyTo == null || replyTo.isEmpty()) {
|
||||||
return "/" + formattedDateTime(createdAt() ?: 0) + ";"
|
return LevelSignature(
|
||||||
|
signature = "/" + formattedDateTime(createdAt() ?: 0) + ";",
|
||||||
|
createdAt = createdAt(),
|
||||||
|
author = author
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val mySignature = (
|
val parent = (
|
||||||
replyTo
|
replyTo
|
||||||
.filter { it in eventsToConsider } // This forces the signature to be based on a branch, avoiding two roots
|
.filter { it in eventsToConsider } // This forces the signature to be based on a branch, avoiding two roots
|
||||||
.map {
|
.map {
|
||||||
cachedSignatures[it] ?: it.replyLevelSignature(eventsToConsider, cachedSignatures).apply { cachedSignatures.put(it, this) }
|
cachedSignatures[it] ?: it.replyLevelSignature(
|
||||||
|
eventsToConsider,
|
||||||
|
cachedSignatures,
|
||||||
|
account,
|
||||||
|
accountFollowingSet,
|
||||||
|
now
|
||||||
|
).apply { cachedSignatures.put(it, this) }
|
||||||
}
|
}
|
||||||
.maxByOrNull { it.length }?.removeSuffix(";") ?: ""
|
.maxByOrNull { it.signature.length }
|
||||||
) + "/" + formattedDateTime(createdAt() ?: 0) + ";"
|
)
|
||||||
|
|
||||||
|
val parentSignature = parent?.signature?.removeSuffix(";") ?: ""
|
||||||
|
|
||||||
|
val threadOrder = if (parent?.author == author && createdAt() != null) {
|
||||||
|
// author of the thread first, in **ascending** order
|
||||||
|
"9" + formattedDateTime((parent?.createdAt ?: 0) + (now - (createdAt() ?: 0)))
|
||||||
|
} else if (author?.pubkeyHex == account.pubkeyHex) {
|
||||||
|
"8" + formattedDateTime(createdAt() ?: 0) // my replies
|
||||||
|
} else if (author?.pubkeyHex in accountFollowingSet) {
|
||||||
|
"7" + formattedDateTime(createdAt() ?: 0) // my follows replies.
|
||||||
|
} else {
|
||||||
|
"0" + formattedDateTime(createdAt() ?: 0) // everyone else.
|
||||||
|
}
|
||||||
|
|
||||||
|
val mySignature = LevelSignature(
|
||||||
|
signature = parentSignature + "/" + threadOrder + ";",
|
||||||
|
createdAt = createdAt(),
|
||||||
|
author = author
|
||||||
|
)
|
||||||
|
|
||||||
cachedSignatures[this] = mySignature
|
cachedSignatures[this] = mySignature
|
||||||
return mySignature
|
return mySignature
|
||||||
|
|||||||
@@ -1,22 +1,29 @@
|
|||||||
package com.vitorpamplona.amethyst.ui.dal
|
package com.vitorpamplona.amethyst.ui.dal
|
||||||
|
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
|
import com.vitorpamplona.amethyst.model.KIND3_FOLLOWS
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.ThreadAssembler
|
import com.vitorpamplona.amethyst.model.ThreadAssembler
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
class ThreadFeedFilter(val noteId: String) : FeedFilter<Note>() {
|
class ThreadFeedFilter(val account: Account, val noteId: String) : FeedFilter<Note>() {
|
||||||
|
|
||||||
override fun feedKey(): String {
|
override fun feedKey(): String {
|
||||||
return noteId
|
return noteId
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun feed(): List<Note> {
|
override fun feed(): List<Note> {
|
||||||
val cachedSignatures: MutableMap<Note, String> = mutableMapOf()
|
val cachedSignatures: MutableMap<Note, Note.LevelSignature> = mutableMapOf()
|
||||||
val eventsToWatch = ThreadAssembler().findThreadFor(noteId) ?: emptySet()
|
val followingSet = account.selectedUsersFollowList(KIND3_FOLLOWS) ?: emptySet()
|
||||||
|
val eventsToWatch = ThreadAssembler().findThreadFor(noteId)
|
||||||
|
val now = TimeUtils.now()
|
||||||
|
|
||||||
// Currently orders by date of each event, descending, at each level of the reply stack
|
// Currently orders by date of each event, descending, at each level of the reply stack
|
||||||
val order = compareByDescending<Note> { it.replyLevelSignature(eventsToWatch, cachedSignatures) }
|
val order = compareByDescending<Note> {
|
||||||
|
it.replyLevelSignature(eventsToWatch, cachedSignatures, account.userProfile(), followingSet, now).signature
|
||||||
|
}
|
||||||
|
|
||||||
return eventsToWatch.sortedWith(order)
|
return eventsToWatch.sortedWith(order)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,10 +95,10 @@ class NostrDiscoverChatFeedViewModel(val account: Account) : FeedViewModel(Disco
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NostrThreadFeedViewModel(val noteId: String) : FeedViewModel(ThreadFeedFilter(noteId)) {
|
class NostrThreadFeedViewModel(account: Account, noteId: String) : FeedViewModel(ThreadFeedFilter(account, noteId)) {
|
||||||
class Factory(val noteId: String) : ViewModelProvider.Factory {
|
class Factory(val account: Account, val noteId: String) : ViewModelProvider.Factory {
|
||||||
override fun <NostrThreadFeedViewModel : ViewModel> create(modelClass: Class<NostrThreadFeedViewModel>): NostrThreadFeedViewModel {
|
override fun <NostrThreadFeedViewModel : ViewModel> create(modelClass: Class<NostrThreadFeedViewModel>): NostrThreadFeedViewModel {
|
||||||
return NostrThreadFeedViewModel(noteId) as NostrThreadFeedViewModel
|
return NostrThreadFeedViewModel(account, noteId) as NostrThreadFeedViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, nav: (Stri
|
|||||||
|
|
||||||
val feedViewModel: NostrThreadFeedViewModel = viewModel(
|
val feedViewModel: NostrThreadFeedViewModel = viewModel(
|
||||||
key = noteId + "NostrThreadFeedViewModel",
|
key = noteId + "NostrThreadFeedViewModel",
|
||||||
factory = NostrThreadFeedViewModel.Factory(noteId)
|
factory = NostrThreadFeedViewModel.Factory(accountViewModel.account, noteId)
|
||||||
)
|
)
|
||||||
|
|
||||||
NostrThreadDataSource.loadThread(noteId)
|
NostrThreadDataSource.loadThread(noteId)
|
||||||
|
|||||||
Reference in New Issue
Block a user