diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt index b17995e1a..f7064557a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -20,6 +20,7 @@ import java.util.Locale import java.util.concurrent.atomic.AtomicBoolean import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -391,13 +392,13 @@ class Account( } init { - userProfile().subscribe(object: User.Listener() { - override fun onRelayChange() { + userProfile().liveRelays.observeForever { + GlobalScope.launch(Dispatchers.IO) { Client.disconnect() Client.connect(activeRelays() ?: convertLocalRelays()) RelayPool.requestAndWatch() } - }) + } } // Observers line up here. diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt index e9e1f6ecd..21b59f49d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -431,7 +431,7 @@ object LocalCache { 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. mentions.forEach { @@ -461,7 +461,7 @@ object LocalCache { 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. mentions.forEach { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt index 1ad4f648e..6e3455600 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.model import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.lnurl.LnInvoiceUtil +import com.vitorpamplona.amethyst.service.NostrHomeDataSource import com.vitorpamplona.amethyst.service.NostrSingleUserDataSource import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.ReportEvent @@ -89,29 +90,14 @@ class User(val pubkeyHex: String) { liveFollows.invalidateData() user.liveFollows.invalidateData() - - updateSubscribers { - it.onFollowsChange() - } - - user.updateSubscribers { - it.onFollowsChange() - } } + fun unfollow(user: User) { follows = follows - user user.followers = user.followers - this liveFollows.invalidateData() user.liveFollows.invalidateData() - - updateSubscribers { - it.onFollowsChange() - } - - user.updateSubscribers { - it.onFollowsChange() - } } fun follow(users: Set, followedAt: Long) { @@ -119,42 +105,31 @@ class User(val pubkeyHex: String) { users.forEach { it.followers = it.followers + this it.liveFollows.invalidateData() - it.updateSubscribers { - it.onFollowsChange() - } } liveFollows.invalidateData() - updateSubscribers { - it.onFollowsChange() - } } + fun unfollow(users: Set) { follows = follows - users users.forEach { it.followers = it.followers - this it.liveFollows.invalidateData() - it.updateSubscribers { - it.onFollowsChange() - } } liveFollows.invalidateData() - updateSubscribers { - it.onFollowsChange() - } } fun addTaggedPost(note: Note) { if (note !in taggedPosts) { taggedPosts = taggedPosts + note - updateSubscribers { it.onNewTaggedPosts() } + // No need for Listener yet } } fun addNote(note: Note) { if (note !in notes) { notes = notes + note - updateSubscribers { it.onNewNotes() } + // No need for Listener yet } } @@ -216,7 +191,6 @@ class User(val pubkeyHex: String) { if (msg !in channel) { messages = messages + Pair(user, channel + msg) liveMessages.invalidateData() - updateSubscribers { it.onNewMessage() } } } @@ -237,7 +211,6 @@ class User(val pubkeyHex: String) { here.counter++ } - updateSubscribers { it.onNewRelayInfo() } liveRelayInfo.invalidateData() } @@ -251,17 +224,11 @@ class User(val pubkeyHex: String) { updatedFollowsAt = updateAt } - data class RelayMetadata(val read: Boolean, val write: Boolean, val activeTypes: Set) - fun updateRelays(relayUse: Map) { if (relays != relayUse) { relays = relayUse - listeners.forEach { - it.onRelayChange() - } + liveRelays.invalidateData() } - - liveRelays.invalidateData() } fun updateUserInfo(newUserInfo: UserMetadata, updateAt: Long) { @@ -287,45 +254,6 @@ class User(val pubkeyHex: String) { } != null } - // Model Observers - private var listeners = setOf() - - 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. val liveFollows: UserLiveData = UserLiveData(this) val liveReports: UserLiveData = UserLiveData(this) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt index c8ac2ba82..b915548d2 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrHomeDataSource.kt @@ -2,11 +2,16 @@ package com.vitorpamplona.amethyst.service import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache +import com.vitorpamplona.amethyst.model.LocalCacheState import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.relays.FeedType 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.events.TextNoteEvent import nostr.postr.toHex @@ -14,22 +19,26 @@ import nostr.postr.toHex object NostrHomeDataSource: NostrDataSource("HomeFeed") { lateinit var account: Account - object cacheListener: User.Listener() { - override fun onFollowsChange() { - resetFilters() - } + private val cacheListener: (UserState) -> Unit = { + resetFilters() } override fun start() { - if (this::account.isInitialized) - account.userProfile().subscribe(cacheListener) + if (this::account.isInitialized) { + GlobalScope.launch(Dispatchers.Main) { + account.userProfile().liveFollows.observeForever(cacheListener) + } + } super.start() } override fun stop() { super.stop() - if (this::account.isInitialized) - account.userProfile().unsubscribe(cacheListener) + if (this::account.isInitialized) { + GlobalScope.launch(Dispatchers.Main) { + account.userProfile().liveFollows.removeObserver(cacheListener) + } + } } fun createFollowAccountsFilter(): TypedFilter { 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 a7b692a3e..6d5bdc64e 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 @@ -78,34 +78,38 @@ abstract class FeedViewModel(val dataSource: NostrDataSource): ViewModel() } fun refresh() { - viewModelScope.launch(Dispatchers.Default) { - val notes = newListFromDataSource() + val scope = CoroutineScope(Job() + Dispatchers.Default) + scope.launch { + refreshSuspended() + } + } - val oldNotesState = feedContent.value - if (oldNotesState is FeedState.Loaded) { - if (notes != oldNotesState.feed) { - withContext(Dispatchers.Main) { - updateFeed(notes) - } - } - } else { - withContext(Dispatchers.Main) { - updateFeed(notes) - } + fun refreshSuspended() { + val notes = newListFromDataSource() + + val oldNotesState = feedContent.value + if (oldNotesState is FeedState.Loaded) { + if (notes != oldNotesState.feed) { + updateFeed(notes) } + } else { + updateFeed(notes) } } private fun updateFeed(notes: List) { - val currentState = feedContent.value + val scope = CoroutineScope(Job() + Dispatchers.Main) + scope.launch { + val currentState = feedContent.value - if (notes.isEmpty()) { - _feedContent.update { FeedState.Empty } - } else if (currentState is FeedState.Loaded) { - // updates the current list - currentState.feed.value = notes - } else { - _feedContent.update { FeedState.Loaded(mutableStateOf(notes)) } + if (notes.isEmpty()) { + _feedContent.update { FeedState.Empty } + } else if (currentState is FeedState.Loaded) { + // updates the current list + currentState.feed.value = notes + } else { + _feedContent.update { FeedState.Loaded(mutableStateOf(notes)) } + } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt index c60eb0d89..1a79e0084 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt @@ -20,6 +20,8 @@ import androidx.navigation.NavController import com.google.accompanist.swiperefresh.SwipeRefresh import com.google.accompanist.swiperefresh.rememberSwipeRefreshState 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.note.RelayCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -59,21 +61,20 @@ class RelayFeedViewModel: ViewModel() { } } - inner class CacheListener: User.Listener() { - override fun onNewRelayInfo() { invalidateData() } - override fun onRelayChange() { invalidateData() } + val listener: (UserState) -> Unit = { + invalidateData() } - val listener = CacheListener() - fun subscribeTo(user: User) { currentUser = user - user.subscribe(listener) + user.liveRelays.observeForever(listener) + user.liveRelayInfo.observeForever(listener) invalidateData() } fun unsubscribeTo(user: User) { - user.unsubscribe(listener) + user.liveRelays.removeObserver(listener) + user.liveRelayInfo.removeObserver(listener) currentUser = null }