diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/DiscoverLiveFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/DiscoverLiveFeedFilter.kt index 500a6cddd5..b77ddd849f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/DiscoverLiveFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/nip53LiveActivities/DiscoverLiveFeedFilter.kt @@ -106,10 +106,17 @@ open class DiscoverLiveFeedFilter( val allParticipants = items.associate { it to counter.countFollowsThatParticipateOn(it, null) } + // Snapshots the status order once per item. convertStatusToOrder reads the + // OnlineChecker cache and the moving five-minute window, both of which can change + // mid-sort (a background online check can update the cache). Reading it lazily inside + // the comparator makes the ordering unstable and TimSort throws + // "Comparison method violates its general contract!". + val statusOrders = items.associateWith { convertStatusToOrder(it.event) } + return items .sortedWith( compareBy( - { convertStatusToOrder(it.event) }, + { statusOrders[it] }, { participantCounts[it] }, { allParticipants[it] }, { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/dal/LiveStreamsFeedFilter.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/dal/LiveStreamsFeedFilter.kt index 12608632b1..c8ed90f0bd 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/dal/LiveStreamsFeedFilter.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/livestreams/dal/LiveStreamsFeedFilter.kt @@ -106,10 +106,17 @@ class LiveStreamsFeedFilter( val allParticipants = items.associate { it to counter.countFollowsThatParticipateOn(it, null) } + // Snapshots the status order once per item. convertStatusToOrder reads the + // OnlineChecker cache and the moving five-minute window, both of which can change + // mid-sort (a background online check can update the cache). Reading it lazily inside + // the comparator makes the ordering unstable and TimSort throws + // "Comparison method violates its general contract!". + val statusOrders = items.associateWith { convertStatusToOrder(it.event) } + return items .sortedWith( compareBy( - { convertStatusToOrder(it.event) }, + { statusOrders[it] }, { participantCounts[it] }, { allParticipants[it] }, {