Adds an isRefreshing status for all feeds.

This commit is contained in:
Vitor Pamplona
2024-09-06 17:19:10 -04:00
parent f12cf93e9f
commit c7fcc47b11
7 changed files with 114 additions and 59 deletions
@@ -21,7 +21,11 @@
package com.vitorpamplona.amethyst.ui.feeds package com.vitorpamplona.amethyst.ui.feeds
import android.util.Log import android.util.Log
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.service.checkNotInMainThread
@@ -54,6 +58,8 @@ class FeedContentState(
private var lastFeedKey: String? = null private var lastFeedKey: String? = null
override val isRefreshing: MutableState<Boolean> = mutableStateOf(false)
fun sendToTop() { fun sendToTop() {
if (scrolltoTopPending) return if (scrolltoTopPending) return
@@ -72,16 +78,21 @@ class FeedContentState(
fun refreshSuspended() { fun refreshSuspended() {
checkNotInMainThread() checkNotInMainThread()
lastFeedKey = localFilter.feedKey() isRefreshing.value = true
val notes = localFilter.loadTop().distinctBy { it.idHex }.toImmutableList() try {
lastFeedKey = localFilter.feedKey()
val notes = localFilter.loadTop().distinctBy { it.idHex }.toImmutableList()
val oldNotesState = _feedContent.value val oldNotesState = _feedContent.value
if (oldNotesState is FeedState.Loaded) { if (oldNotesState is FeedState.Loaded) {
if (!equalImmutableLists(notes, oldNotesState.feed.value.list)) { if (!equalImmutableLists(notes, oldNotesState.feed.value.list)) {
updateFeed(notes)
}
} else {
updateFeed(notes) updateFeed(notes)
} }
} else { } finally {
updateFeed(notes) isRefreshing.value = false
} }
} }
@@ -20,6 +20,10 @@
*/ */
package com.vitorpamplona.amethyst.ui.feeds package com.vitorpamplona.amethyst.ui.feeds
import androidx.compose.runtime.MutableState
interface InvalidatableContent { interface InvalidatableContent {
fun invalidateData(ignoreIfDoing: Boolean = false) fun invalidateData(ignoreIfDoing: Boolean = false)
val isRefreshing: MutableState<Boolean>
} }
@@ -260,6 +260,8 @@ abstract class FeedViewModel(
InvalidatableContent { InvalidatableContent {
val feedState = FeedContentState(localFilter, viewModelScope) val feedState = FeedContentState(localFilter, viewModelScope)
override val isRefreshing = feedState.isRefreshing
fun sendToTop() = feedState.sendToTop() fun sendToTop() = feedState.sendToTop()
suspend fun sentToTop() = feedState.sentToTop() suspend fun sentToTop() = feedState.sentToTop()
@@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.ui.screen package com.vitorpamplona.amethyst.ui.screen
import android.util.Log import android.util.Log
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -107,19 +108,27 @@ open class UserFeedViewModel(
viewModelScope.launch(Dispatchers.Default) { refreshSuspended() } viewModelScope.launch(Dispatchers.Default) { refreshSuspended() }
} }
override val isRefreshing: MutableState<Boolean> = mutableStateOf(false)
private fun refreshSuspended() { private fun refreshSuspended() {
checkNotInMainThread() checkNotInMainThread()
val notes = dataSource.loadTop().toImmutableList() try {
isRefreshing.value = true
val oldNotesState = _feedContent.value val notes = dataSource.loadTop().toImmutableList()
if (oldNotesState is UserFeedState.Loaded) {
// Using size as a proxy for has changed. val oldNotesState = _feedContent.value
if (!equalImmutableLists(notes, oldNotesState.feed.value)) { if (oldNotesState is UserFeedState.Loaded) {
// Using size as a proxy for has changed.
if (!equalImmutableLists(notes, oldNotesState.feed.value)) {
updateFeed(notes)
}
} else {
updateFeed(notes) updateFeed(notes)
} }
} else { } finally {
updateFeed(notes) isRefreshing.value = false
} }
} }
@@ -22,7 +22,9 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications
import android.util.Log import android.util.Log
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
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.Note import com.vitorpamplona.amethyst.model.Note
@@ -72,6 +74,8 @@ class CardFeedContentState(
private var lastFeedKey: String? = null private var lastFeedKey: String? = null
override val isRefreshing: MutableState<Boolean> = mutableStateOf(false)
fun sendToTop() { fun sendToTop() {
if (scrolltoTopPending) return if (scrolltoTopPending) return
@@ -94,41 +98,47 @@ class CardFeedContentState(
private fun refreshSuspended() { private fun refreshSuspended() {
checkNotInMainThread() checkNotInMainThread()
val notes = localFilter.feed() try {
lastFeedKey = localFilter.feedKey() isRefreshing.value = true
val thisAccount = (localFilter as? NotificationFeedFilter)?.account val notes = localFilter.feed()
val lastNotesCopy = if (thisAccount == lastAccount) lastNotes else null lastFeedKey = localFilter.feedKey()
val oldNotesState = _feedContent.value val thisAccount = (localFilter as? NotificationFeedFilter)?.account
if (lastNotesCopy != null && oldNotesState is CardFeedState.Loaded) { val lastNotesCopy = if (thisAccount == lastAccount) lastNotes else null
val newCards = convertToCard(notes.minus(lastNotesCopy))
if (newCards.isNotEmpty()) { val oldNotesState = _feedContent.value
if (lastNotesCopy != null && oldNotesState is CardFeedState.Loaded) {
val newCards = convertToCard(notes.minus(lastNotesCopy))
if (newCards.isNotEmpty()) {
lastNotes = notes.toSet()
lastAccount = (localFilter as? NotificationFeedFilter)?.account
val updatedCards =
(oldNotesState.feed.value.list + newCards)
.distinctBy { it.id() }
.sortedWith(DefaultFeedOrderCard)
.take(localFilter.limit())
.toImmutableList()
if (!equalImmutableLists(oldNotesState.feed.value.list, updatedCards)) {
updateFeed(updatedCards)
}
}
} else {
lastNotes = notes.toSet() lastNotes = notes.toSet()
lastAccount = (localFilter as? NotificationFeedFilter)?.account lastAccount = (localFilter as? NotificationFeedFilter)?.account
val updatedCards = val cards =
(oldNotesState.feed.value.list + newCards) convertToCard(notes)
.distinctBy { it.id() }
.sortedWith(DefaultFeedOrderCard) .sortedWith(DefaultFeedOrderCard)
.take(localFilter.limit()) .take(localFilter.limit())
.toImmutableList() .toImmutableList()
if (!equalImmutableLists(oldNotesState.feed.value.list, updatedCards)) { updateFeed(cards)
updateFeed(updatedCards)
}
} }
} else { } finally {
lastNotes = notes.toSet() isRefreshing.value = false
lastAccount = (localFilter as? NotificationFeedFilter)?.account
val cards =
convertToCard(notes)
.sortedWith(DefaultFeedOrderCard)
.take(localFilter.limit())
.toImmutableList()
updateFeed(cards)
} }
} }
@@ -21,7 +21,9 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile package com.vitorpamplona.amethyst.ui.screen.loggedIn.profile
import android.util.Log import android.util.Log
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.RelayInfo import com.vitorpamplona.amethyst.model.RelayInfo
@@ -50,28 +52,36 @@ class RelayFeedViewModel :
var currentUser: User? = null var currentUser: User? = null
override val isRefreshing: MutableState<Boolean> = mutableStateOf(false)
fun refresh() { fun refresh() {
viewModelScope.launch(Dispatchers.Default) { refreshSuspended() } viewModelScope.launch(Dispatchers.Default) { refreshSuspended() }
} }
fun refreshSuspended() { fun refreshSuspended() {
val beingUsed = currentUser?.relaysBeingUsed?.values ?: emptyList() try {
val beingUsedSet = currentUser?.relaysBeingUsed?.keys ?: emptySet() isRefreshing.value = true
val newRelaysFromRecord = val beingUsed = currentUser?.relaysBeingUsed?.values ?: emptyList()
currentUser?.latestContactList?.relays()?.entries?.mapNotNullTo(HashSet()) { val beingUsedSet = currentUser?.relaysBeingUsed?.keys ?: emptySet()
val url = RelayUrlFormatter.normalize(it.key)
if (url !in beingUsedSet) { val newRelaysFromRecord =
RelayInfo(url, 0, 0) currentUser?.latestContactList?.relays()?.entries?.mapNotNullTo(HashSet()) {
} else { val url = RelayUrlFormatter.normalize(it.key)
null if (url !in beingUsedSet) {
RelayInfo(url, 0, 0)
} else {
null
}
} }
} ?: emptyList()
?: emptyList()
val newList = (beingUsed + newRelaysFromRecord).sortedWith(order) val newList = (beingUsed + newRelaysFromRecord).sortedWith(order)
_feedContent.update { newList } _feedContent.update { newList }
} finally {
isRefreshing.value = false
}
} }
val listener: (UserState) -> Unit = { invalidateData() } val listener: (UserState) -> Unit = { invalidateData() }
@@ -21,6 +21,7 @@
package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings package com.vitorpamplona.amethyst.ui.screen.loggedIn.settings
import android.util.Log import android.util.Log
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@@ -63,6 +64,8 @@ open class StringFeedViewModel(
private val _feedContent = MutableStateFlow<StringFeedState>(StringFeedState.Loading) private val _feedContent = MutableStateFlow<StringFeedState>(StringFeedState.Loading)
val feedContent = _feedContent.asStateFlow() val feedContent = _feedContent.asStateFlow()
override val isRefreshing: MutableState<Boolean> = mutableStateOf(false)
private fun refresh() { private fun refresh() {
viewModelScope.launch(Dispatchers.Default) { refreshSuspended() } viewModelScope.launch(Dispatchers.Default) { refreshSuspended() }
} }
@@ -70,16 +73,22 @@ open class StringFeedViewModel(
private fun refreshSuspended() { private fun refreshSuspended() {
checkNotInMainThread() checkNotInMainThread()
val notes = dataSource.loadTop().toImmutableList() try {
isRefreshing.value = true
val oldNotesState = _feedContent.value val notes = dataSource.loadTop().toImmutableList()
if (oldNotesState is StringFeedState.Loaded) {
// Using size as a proxy for has changed. val oldNotesState = _feedContent.value
if (!equalImmutableLists(notes, oldNotesState.feed.value)) { if (oldNotesState is StringFeedState.Loaded) {
// Using size as a proxy for has changed.
if (!equalImmutableLists(notes, oldNotesState.feed.value)) {
updateFeed(notes)
}
} else {
updateFeed(notes) updateFeed(notes)
} }
} else { } finally {
updateFeed(notes) isRefreshing.value = false
} }
} }