From f06b5fdacf74728cd04f669b8dde814530bcf040 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Thu, 31 Aug 2023 16:41:02 -0400 Subject: [PATCH] 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. --- .../com/vitorpamplona/amethyst/model/Note.kt | 48 ++++++++++++++++--- .../amethyst/ui/dal/ThreadFeedFilter.kt | 15 ++++-- .../amethyst/ui/screen/FeedViewModel.kt | 6 +-- .../ui/screen/loggedIn/ThreadScreen.kt | 2 +- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt index 5f84ed01c..ad0daf0fe 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -136,26 +136,60 @@ open class Note(val idHex: String) { .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 */ fun replyLevelSignature( eventsToConsider: Set, - cachedSignatures: MutableMap - ): String { + cachedSignatures: MutableMap, + account: User, + accountFollowingSet: Set, + now: Long + ): LevelSignature { val replyTo = replyTo 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 .filter { it in eventsToConsider } // This forces the signature to be based on a branch, avoiding two roots .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(";") ?: "" - ) + "/" + formattedDateTime(createdAt() ?: 0) + ";" + .maxByOrNull { it.signature.length } + ) + + 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 return mySignature diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ThreadFeedFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ThreadFeedFilter.kt index 411d37104..0f028d4bf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ThreadFeedFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/dal/ThreadFeedFilter.kt @@ -1,22 +1,29 @@ package com.vitorpamplona.amethyst.ui.dal 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.ThreadAssembler +import com.vitorpamplona.quartz.utils.TimeUtils @Immutable -class ThreadFeedFilter(val noteId: String) : FeedFilter() { +class ThreadFeedFilter(val account: Account, val noteId: String) : FeedFilter() { override fun feedKey(): String { return noteId } override fun feed(): List { - val cachedSignatures: MutableMap = mutableMapOf() - val eventsToWatch = ThreadAssembler().findThreadFor(noteId) ?: emptySet() + val cachedSignatures: MutableMap = mutableMapOf() + 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 - val order = compareByDescending { it.replyLevelSignature(eventsToWatch, cachedSignatures) } + val order = compareByDescending { + it.replyLevelSignature(eventsToWatch, cachedSignatures, account.userProfile(), followingSet, now).signature + } return eventsToWatch.sortedWith(order) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index 7561b818d..f245ee538 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -95,10 +95,10 @@ class NostrDiscoverChatFeedViewModel(val account: Account) : FeedViewModel(Disco } } -class NostrThreadFeedViewModel(val noteId: String) : FeedViewModel(ThreadFeedFilter(noteId)) { - class Factory(val noteId: String) : ViewModelProvider.Factory { +class NostrThreadFeedViewModel(account: Account, noteId: String) : FeedViewModel(ThreadFeedFilter(account, noteId)) { + class Factory(val account: Account, val noteId: String) : ViewModelProvider.Factory { override fun create(modelClass: Class): NostrThreadFeedViewModel { - return NostrThreadFeedViewModel(noteId) as NostrThreadFeedViewModel + return NostrThreadFeedViewModel(account, noteId) as NostrThreadFeedViewModel } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt index a7107e819..8df68e327 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ThreadScreen.kt @@ -23,7 +23,7 @@ fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, nav: (Stri val feedViewModel: NostrThreadFeedViewModel = viewModel( key = noteId + "NostrThreadFeedViewModel", - factory = NostrThreadFeedViewModel.Factory(noteId) + factory = NostrThreadFeedViewModel.Factory(accountViewModel.account, noteId) ) NostrThreadDataSource.loadThread(noteId)