mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-07-13 01:16:44 +02:00
Removing the Duplicated Observer infrastructure on User: It wasn't updating observers correctly due to the batch update we use.
This commit is contained in:
@ -20,6 +20,7 @@ import java.util.Locale
|
|||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -391,13 +392,13 @@ class Account(
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
userProfile().subscribe(object: User.Listener() {
|
userProfile().liveRelays.observeForever {
|
||||||
override fun onRelayChange() {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
Client.disconnect()
|
Client.disconnect()
|
||||||
Client.connect(activeRelays() ?: convertLocalRelays())
|
Client.connect(activeRelays() ?: convertLocalRelays())
|
||||||
RelayPool.requestAndWatch()
|
RelayPool.requestAndWatch()
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Observers line up here.
|
// Observers line up here.
|
||||||
|
@ -431,7 +431,7 @@ object LocalCache {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d("ZP", "New ZapEvent ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
//Log.d("ZP", "New ZapEvent ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
// Adds notifications to users.
|
// Adds notifications to users.
|
||||||
mentions.forEach {
|
mentions.forEach {
|
||||||
@ -461,7 +461,7 @@ object LocalCache {
|
|||||||
|
|
||||||
note.loadEvent(event, author, mentions, repliesTo)
|
note.loadEvent(event, author, mentions, repliesTo)
|
||||||
|
|
||||||
Log.d("ZP", "New Zap Request ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
//Log.d("ZP", "New Zap Request ${event.content} (${notes.size},${users.size}) ${note.author?.toBestDisplayName()} ${formattedDateTime(event.createdAt)}")
|
||||||
|
|
||||||
// Adds notifications to users.
|
// Adds notifications to users.
|
||||||
mentions.forEach {
|
mentions.forEach {
|
||||||
|
@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.model
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import com.vitorpamplona.amethyst.lnurl.LnInvoiceUtil
|
import com.vitorpamplona.amethyst.lnurl.LnInvoiceUtil
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||||
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource
|
||||||
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
import com.vitorpamplona.amethyst.service.model.LnZapEvent
|
||||||
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
import com.vitorpamplona.amethyst.service.model.ReportEvent
|
||||||
@ -89,29 +90,14 @@ class User(val pubkeyHex: String) {
|
|||||||
|
|
||||||
liveFollows.invalidateData()
|
liveFollows.invalidateData()
|
||||||
user.liveFollows.invalidateData()
|
user.liveFollows.invalidateData()
|
||||||
|
|
||||||
updateSubscribers {
|
|
||||||
it.onFollowsChange()
|
|
||||||
}
|
|
||||||
|
|
||||||
user.updateSubscribers {
|
|
||||||
it.onFollowsChange()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unfollow(user: User) {
|
fun unfollow(user: User) {
|
||||||
follows = follows - user
|
follows = follows - user
|
||||||
user.followers = user.followers - this
|
user.followers = user.followers - this
|
||||||
|
|
||||||
liveFollows.invalidateData()
|
liveFollows.invalidateData()
|
||||||
user.liveFollows.invalidateData()
|
user.liveFollows.invalidateData()
|
||||||
|
|
||||||
updateSubscribers {
|
|
||||||
it.onFollowsChange()
|
|
||||||
}
|
|
||||||
|
|
||||||
user.updateSubscribers {
|
|
||||||
it.onFollowsChange()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun follow(users: Set<User>, followedAt: Long) {
|
fun follow(users: Set<User>, followedAt: Long) {
|
||||||
@ -119,42 +105,31 @@ class User(val pubkeyHex: String) {
|
|||||||
users.forEach {
|
users.forEach {
|
||||||
it.followers = it.followers + this
|
it.followers = it.followers + this
|
||||||
it.liveFollows.invalidateData()
|
it.liveFollows.invalidateData()
|
||||||
it.updateSubscribers {
|
|
||||||
it.onFollowsChange()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
liveFollows.invalidateData()
|
liveFollows.invalidateData()
|
||||||
updateSubscribers {
|
|
||||||
it.onFollowsChange()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unfollow(users: Set<User>) {
|
fun unfollow(users: Set<User>) {
|
||||||
follows = follows - users
|
follows = follows - users
|
||||||
users.forEach {
|
users.forEach {
|
||||||
it.followers = it.followers - this
|
it.followers = it.followers - this
|
||||||
it.liveFollows.invalidateData()
|
it.liveFollows.invalidateData()
|
||||||
it.updateSubscribers {
|
|
||||||
it.onFollowsChange()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
liveFollows.invalidateData()
|
liveFollows.invalidateData()
|
||||||
updateSubscribers {
|
|
||||||
it.onFollowsChange()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addTaggedPost(note: Note) {
|
fun addTaggedPost(note: Note) {
|
||||||
if (note !in taggedPosts) {
|
if (note !in taggedPosts) {
|
||||||
taggedPosts = taggedPosts + note
|
taggedPosts = taggedPosts + note
|
||||||
updateSubscribers { it.onNewTaggedPosts() }
|
// No need for Listener yet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addNote(note: Note) {
|
fun addNote(note: Note) {
|
||||||
if (note !in notes) {
|
if (note !in notes) {
|
||||||
notes = notes + note
|
notes = notes + note
|
||||||
updateSubscribers { it.onNewNotes() }
|
// No need for Listener yet
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -216,7 +191,6 @@ class User(val pubkeyHex: String) {
|
|||||||
if (msg !in channel) {
|
if (msg !in channel) {
|
||||||
messages = messages + Pair(user, channel + msg)
|
messages = messages + Pair(user, channel + msg)
|
||||||
liveMessages.invalidateData()
|
liveMessages.invalidateData()
|
||||||
updateSubscribers { it.onNewMessage() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +211,6 @@ class User(val pubkeyHex: String) {
|
|||||||
here.counter++
|
here.counter++
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSubscribers { it.onNewRelayInfo() }
|
|
||||||
liveRelayInfo.invalidateData()
|
liveRelayInfo.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,17 +224,11 @@ class User(val pubkeyHex: String) {
|
|||||||
updatedFollowsAt = updateAt
|
updatedFollowsAt = updateAt
|
||||||
}
|
}
|
||||||
|
|
||||||
data class RelayMetadata(val read: Boolean, val write: Boolean, val activeTypes: Set<FeedType>)
|
|
||||||
|
|
||||||
fun updateRelays(relayUse: Map<String, ContactListEvent.ReadWrite>) {
|
fun updateRelays(relayUse: Map<String, ContactListEvent.ReadWrite>) {
|
||||||
if (relays != relayUse) {
|
if (relays != relayUse) {
|
||||||
relays = relayUse
|
relays = relayUse
|
||||||
listeners.forEach {
|
liveRelays.invalidateData()
|
||||||
it.onRelayChange()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
liveRelays.invalidateData()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateUserInfo(newUserInfo: UserMetadata, updateAt: Long) {
|
fun updateUserInfo(newUserInfo: UserMetadata, updateAt: Long) {
|
||||||
@ -287,45 +254,6 @@ class User(val pubkeyHex: String) {
|
|||||||
} != null
|
} != null
|
||||||
}
|
}
|
||||||
|
|
||||||
// Model Observers
|
|
||||||
private var listeners = setOf<Listener>()
|
|
||||||
|
|
||||||
fun subscribe(listener: Listener) {
|
|
||||||
listeners = listeners.plus(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unsubscribe(listener: Listener) {
|
|
||||||
listeners = listeners.minus(listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class Listener {
|
|
||||||
open fun onRelayChange() = Unit
|
|
||||||
open fun onFollowsChange() = Unit
|
|
||||||
open fun onNewTaggedPosts() = Unit
|
|
||||||
open fun onNewNotes() = Unit
|
|
||||||
open fun onNewMessage() = Unit
|
|
||||||
open fun onNewRelayInfo() = Unit
|
|
||||||
open fun onNewReports() = Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refreshes observers in batches.
|
|
||||||
var modelHandlerWaiting = AtomicBoolean()
|
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
fun updateSubscribers(on: (Listener) -> Unit) {
|
|
||||||
if (modelHandlerWaiting.getAndSet(true)) return
|
|
||||||
|
|
||||||
modelHandlerWaiting.set(true)
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
|
||||||
scope.launch {
|
|
||||||
delay(100)
|
|
||||||
listeners.forEach {
|
|
||||||
on(it)
|
|
||||||
}
|
|
||||||
modelHandlerWaiting.set(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UI Observers line up here.
|
// UI Observers line up here.
|
||||||
val liveFollows: UserLiveData = UserLiveData(this)
|
val liveFollows: UserLiveData = UserLiveData(this)
|
||||||
val liveReports: UserLiveData = UserLiveData(this)
|
val liveReports: UserLiveData = UserLiveData(this)
|
||||||
|
@ -2,11 +2,16 @@ package com.vitorpamplona.amethyst.service
|
|||||||
|
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
|
import com.vitorpamplona.amethyst.model.LocalCacheState
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.model.UserState
|
||||||
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
import com.vitorpamplona.amethyst.service.model.RepostEvent
|
||||||
import com.vitorpamplona.amethyst.service.relays.FeedType
|
import com.vitorpamplona.amethyst.service.relays.FeedType
|
||||||
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
import com.vitorpamplona.amethyst.service.relays.TypedFilter
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import nostr.postr.JsonFilter
|
import nostr.postr.JsonFilter
|
||||||
import nostr.postr.events.TextNoteEvent
|
import nostr.postr.events.TextNoteEvent
|
||||||
import nostr.postr.toHex
|
import nostr.postr.toHex
|
||||||
@ -14,22 +19,26 @@ import nostr.postr.toHex
|
|||||||
object NostrHomeDataSource: NostrDataSource<Note>("HomeFeed") {
|
object NostrHomeDataSource: NostrDataSource<Note>("HomeFeed") {
|
||||||
lateinit var account: Account
|
lateinit var account: Account
|
||||||
|
|
||||||
object cacheListener: User.Listener() {
|
private val cacheListener: (UserState) -> Unit = {
|
||||||
override fun onFollowsChange() {
|
resetFilters()
|
||||||
resetFilters()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun start() {
|
override fun start() {
|
||||||
if (this::account.isInitialized)
|
if (this::account.isInitialized) {
|
||||||
account.userProfile().subscribe(cacheListener)
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
account.userProfile().liveFollows.observeForever(cacheListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
super.start()
|
super.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun stop() {
|
override fun stop() {
|
||||||
super.stop()
|
super.stop()
|
||||||
if (this::account.isInitialized)
|
if (this::account.isInitialized) {
|
||||||
account.userProfile().unsubscribe(cacheListener)
|
GlobalScope.launch(Dispatchers.Main) {
|
||||||
|
account.userProfile().liveFollows.removeObserver(cacheListener)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createFollowAccountsFilter(): TypedFilter {
|
fun createFollowAccountsFilter(): TypedFilter {
|
||||||
|
@ -78,34 +78,38 @@ abstract class FeedViewModel(val dataSource: NostrDataSource<Note>): ViewModel()
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun refresh() {
|
fun refresh() {
|
||||||
viewModelScope.launch(Dispatchers.Default) {
|
val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
val notes = newListFromDataSource()
|
scope.launch {
|
||||||
|
refreshSuspended()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val oldNotesState = feedContent.value
|
fun refreshSuspended() {
|
||||||
if (oldNotesState is FeedState.Loaded) {
|
val notes = newListFromDataSource()
|
||||||
if (notes != oldNotesState.feed) {
|
|
||||||
withContext(Dispatchers.Main) {
|
val oldNotesState = feedContent.value
|
||||||
updateFeed(notes)
|
if (oldNotesState is FeedState.Loaded) {
|
||||||
}
|
if (notes != oldNotesState.feed) {
|
||||||
}
|
updateFeed(notes)
|
||||||
} else {
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
updateFeed(notes)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
updateFeed(notes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateFeed(notes: List<Note>) {
|
private fun updateFeed(notes: List<Note>) {
|
||||||
val currentState = feedContent.value
|
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||||
|
scope.launch {
|
||||||
|
val currentState = feedContent.value
|
||||||
|
|
||||||
if (notes.isEmpty()) {
|
if (notes.isEmpty()) {
|
||||||
_feedContent.update { FeedState.Empty }
|
_feedContent.update { FeedState.Empty }
|
||||||
} else if (currentState is FeedState.Loaded) {
|
} else if (currentState is FeedState.Loaded) {
|
||||||
// updates the current list
|
// updates the current list
|
||||||
currentState.feed.value = notes
|
currentState.feed.value = notes
|
||||||
} else {
|
} else {
|
||||||
_feedContent.update { FeedState.Loaded(mutableStateOf(notes)) }
|
_feedContent.update { FeedState.Loaded(mutableStateOf(notes)) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@ import androidx.navigation.NavController
|
|||||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.model.UserState
|
||||||
|
import com.vitorpamplona.amethyst.service.NostrHomeDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
import com.vitorpamplona.amethyst.ui.actions.NewRelayListView
|
||||||
import com.vitorpamplona.amethyst.ui.note.RelayCompose
|
import com.vitorpamplona.amethyst.ui.note.RelayCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@ -59,21 +61,20 @@ class RelayFeedViewModel: ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class CacheListener: User.Listener() {
|
val listener: (UserState) -> Unit = {
|
||||||
override fun onNewRelayInfo() { invalidateData() }
|
invalidateData()
|
||||||
override fun onRelayChange() { invalidateData() }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val listener = CacheListener()
|
|
||||||
|
|
||||||
fun subscribeTo(user: User) {
|
fun subscribeTo(user: User) {
|
||||||
currentUser = user
|
currentUser = user
|
||||||
user.subscribe(listener)
|
user.liveRelays.observeForever(listener)
|
||||||
|
user.liveRelayInfo.observeForever(listener)
|
||||||
invalidateData()
|
invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unsubscribeTo(user: User) {
|
fun unsubscribeTo(user: User) {
|
||||||
user.unsubscribe(listener)
|
user.liveRelays.removeObserver(listener)
|
||||||
|
user.liveRelayInfo.removeObserver(listener)
|
||||||
currentUser = null
|
currentUser = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user