diff --git a/app/src/main/java/com/vitorpamplona/amethyst/NotificationCache.kt b/app/src/main/java/com/vitorpamplona/amethyst/NotificationCache.kt index 47d5aad066..0dc17365fb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/NotificationCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/NotificationCache.kt @@ -1,14 +1,11 @@ package com.vitorpamplona.amethyst import androidx.lifecycle.LiveData +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.concurrent.atomic.AtomicBoolean object NotificationCache { val lastReadByRoute = mutableMapOf() @@ -41,23 +38,14 @@ object NotificationCache { class NotificationLiveData(val cache: NotificationCache) : LiveData(NotificationState(cache)) { // Refreshes observers in batches. - var handlerWaiting = AtomicBoolean() + private val bundler = BundledUpdate(300, Dispatchers.Main) { + if (hasActiveObservers()) { + refresh() + } + } fun invalidateData() { - if (!hasActiveObservers()) return - if (handlerWaiting.getAndSet(true)) return - - val scope = CoroutineScope(Job() + Dispatchers.Main) - scope.launch { - try { - delay(100) - refresh() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - } + bundler.invalidate() } fun refresh() { 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 6980884ca1..0e2c36ccd8 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Account.kt @@ -24,18 +24,13 @@ import com.vitorpamplona.amethyst.service.relays.Constants import com.vitorpamplona.amethyst.service.relays.FeedType import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.RelayPool -import kotlinx.coroutines.CoroutineScope +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import nostr.postr.Persona import java.util.Locale -import java.util.concurrent.atomic.AtomicBoolean val DefaultChannels = setOf( "25e5c82273a271cb1a840d0060391a0bf4965cafeb029d5ab55350b418953fbb", // -> Anigma's Nostr @@ -795,22 +790,15 @@ class Account( } class AccountLiveData(private val account: Account) : LiveData(AccountState(account)) { - var handlerWaiting = AtomicBoolean() + // Refreshes observers in batches. + private val bundler = BundledUpdate(300, Dispatchers.Default) { + if (hasActiveObservers()) { + refresh() + } + } fun invalidateData() { - if (handlerWaiting.getAndSet(true)) return - - val scope = CoroutineScope(Job() + Dispatchers.Default) - scope.launch { - try { - delay(100) - refresh() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - } + bundler.invalidate() } fun refresh() { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt index dfed1554c8..001069c08f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt @@ -4,14 +4,8 @@ import android.util.Log import android.util.LruCache import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.model.Event -import kotlinx.coroutines.CoroutineScope +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.concurrent.atomic.AtomicBoolean data class Spammer(val pubkeyHex: HexKey, var duplicatedMessages: Set) @@ -60,23 +54,14 @@ class AntiSpamFilter { class AntiSpamLiveData(val cache: AntiSpamFilter) : LiveData(AntiSpamState(cache)) { // Refreshes observers in batches. - var handlerWaiting = AtomicBoolean() + private val bundler = BundledUpdate(300, Dispatchers.Main) { + if (hasActiveObservers()) { + refresh() + } + } fun invalidateData() { - if (!hasActiveObservers()) return - if (handlerWaiting.getAndSet(true)) return - - val scope = CoroutineScope(Job() + Dispatchers.Main) - scope.launch { - try { - delay(100) - refresh() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - } + bundler.invalidate() } private fun refresh() { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt index 1a527c1b99..c5cda76294 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Channel.kt @@ -3,8 +3,10 @@ package com.vitorpamplona.amethyst.model import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.NostrSingleChannelDataSource import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.note.toShortenHex import fr.acinq.secp256k1.Hex +import kotlinx.coroutines.Dispatchers import java.util.concurrent.ConcurrentHashMap class Channel(val idHex: String) { @@ -36,7 +38,7 @@ class Channel(val idHex: String) { this.info = channelInfo this.updatedMetadataAt = updatedAt - live.refresh() + live.invalidateData() } fun profilePicture(): String? { @@ -71,7 +73,18 @@ class Channel(val idHex: String) { } class ChannelLiveData(val channel: Channel) : LiveData(ChannelState(channel)) { - fun refresh() { + // Refreshes observers in batches. + private val bundler = BundledUpdate(300, Dispatchers.Main) { + if (hasActiveObservers()) { + refresh() + } + } + + fun invalidateData() { + bundler.invalidate() + } + + private fun refresh() { postValue(ChannelState(channel)) } 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 852eb9c9fc..23b9c1460f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/LocalCache.kt @@ -27,6 +27,7 @@ import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.model.RepostEvent import com.vitorpamplona.amethyst.service.model.TextNoteEvent import com.vitorpamplona.amethyst.service.relays.Relay +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import fr.acinq.secp256k1.Hex import kotlinx.coroutines.* import nostr.postr.toNpub @@ -35,7 +36,6 @@ import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.atomic.AtomicBoolean object LocalCache { val metadataParser = jacksonObjectMapper() @@ -745,23 +745,14 @@ class LocalCacheLiveData(val cache: LocalCache) : LiveData(LocalCacheState(cache)) { // Refreshes observers in batches. - var handlerWaiting = AtomicBoolean() + private val bundler = BundledUpdate(300, Dispatchers.Main) { + if (hasActiveObservers()) { + refresh() + } + } fun invalidateData() { - if (!hasActiveObservers()) return - if (handlerWaiting.getAndSet(true)) return - - val scope = CoroutineScope(Job() + Dispatchers.Main) - scope.launch { - try { - delay(50) - refresh() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - } + bundler.invalidate() } private fun refresh() { 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 747424e67a..7e542f9fdd 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/Note.kt @@ -4,21 +4,15 @@ import androidx.lifecycle.LiveData import com.vitorpamplona.amethyst.service.NostrSingleEventDataSource import com.vitorpamplona.amethyst.service.model.* import com.vitorpamplona.amethyst.service.relays.Relay +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.note.toShortenHex import fr.acinq.secp256k1.Hex -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.math.BigDecimal import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter import java.util.Date -import java.util.concurrent.atomic.AtomicBoolean import java.util.regex.Pattern val tagSearch = Pattern.compile("(?:\\s|\\A)\\#\\[([0-9]+)\\]") @@ -378,23 +372,14 @@ class NoteLiveSet(u: Note) { class NoteLiveData(val note: Note) : LiveData(NoteState(note)) { // Refreshes observers in batches. - var handlerWaiting = AtomicBoolean() + private val bundler = BundledUpdate(300, Dispatchers.Main) { + if (hasActiveObservers()) { + refresh() + } + } fun invalidateData() { - if (!hasActiveObservers()) return - if (handlerWaiting.getAndSet(true)) return - - val scope = CoroutineScope(Job() + Dispatchers.Main) - scope.launch { - try { - delay(100) - refresh() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - } + bundler.invalidate() } private fun refresh() { 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 6317fa8ed9..1125a98f22 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/User.kt @@ -8,19 +8,13 @@ import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.MetadataEvent import com.vitorpamplona.amethyst.service.model.ReportEvent import com.vitorpamplona.amethyst.service.relays.Relay +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.note.toShortenHex import fr.acinq.secp256k1.Hex -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import nostr.postr.Bech32 import nostr.postr.toNpub import java.math.BigDecimal -import java.util.concurrent.atomic.AtomicBoolean import java.util.regex.Pattern val lnurlpPattern = Pattern.compile("(?i:http|https):\\/\\/((.+)\\/)*\\.well-known\\/lnurlp\\/(.*)") @@ -400,24 +394,15 @@ class UserMetadata { } class UserLiveData(val user: User) : LiveData(UserState(user)) { - // Refreshes observers in batches. - var handlerWaiting = AtomicBoolean() + private val bundler = BundledUpdate(300, Dispatchers.Main) { + if (hasActiveObservers()) { + refresh() + } + } fun invalidateData() { - if (handlerWaiting.getAndSet(true)) return - - val scope = CoroutineScope(Job() + Dispatchers.Main) - scope.launch { - try { - delay(100) - refresh() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - } + bundler.invalidate() } private fun refresh() { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt index 2da6b92236..c698a4946c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/NostrDataSource.kt @@ -27,16 +27,13 @@ import com.vitorpamplona.amethyst.service.model.TextNoteEvent import com.vitorpamplona.amethyst.service.relays.Client import com.vitorpamplona.amethyst.service.relays.Relay import com.vitorpamplona.amethyst.service.relays.Subscription +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.util.Date import java.util.UUID -import java.util.concurrent.atomic.AtomicBoolean abstract class NostrDataSource(val debugName: String) { private var subscriptions = mapOf() @@ -154,24 +151,16 @@ abstract class NostrDataSource(val debugName: String) { } // Refreshes observers in batches. - var handlerWaiting = AtomicBoolean() - - fun invalidateFilters() { - if (handlerWaiting.getAndSet(true)) return - + private val bundler = BundledUpdate(250, Dispatchers.IO) { println("DataSource: ${this.javaClass.simpleName} InvalidateFilters") - val scope = CoroutineScope(Job() + Dispatchers.IO) - scope.launch { - try { - delay(200) - resetFiltersSuspend() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - } + // adds the time to perform the refresh into this delay + // holding off new updates in case of heavy refresh routines. + resetFiltersSuspend() + } + + fun invalidateFilters() { + bundler.invalidate() } fun resetFilters() { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt new file mode 100644 index 0000000000..d2172e560d --- /dev/null +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/BundledUpdate.kt @@ -0,0 +1,46 @@ +package com.vitorpamplona.amethyst.ui.components + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.util.concurrent.atomic.AtomicBoolean + +/** + * This class is designed to have a waiting time between two calls of invalidate + */ +class BundledUpdate( + val delay: Long, + val dispatcher: CoroutineDispatcher = Dispatchers.Default, + val onUpdate: () -> Unit +) { + private var onlyOneInBlock = AtomicBoolean() + private var invalidatesAgain = false + + fun invalidate() { + if (onlyOneInBlock.getAndSet(true)) { + invalidatesAgain = true + return + } + + val scope = CoroutineScope(Job() + dispatcher) + scope.launch { + try { + onUpdate() + delay(delay) + if (invalidatesAgain) { + onUpdate() + } + } finally { + withContext(NonCancellable) { + invalidatesAgain = false + onlyOneInBlock.set(false) + } + } + } + } +} diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreview.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreview.kt index 9405c53627..fd012bc0b4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreview.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/UrlPreview.kt @@ -18,16 +18,16 @@ import kotlinx.coroutines.withContext @Composable fun UrlPreview(url: String, urlText: String) { -// val default = UrlCachedPreviewer.cache[url]?.let { -// if (it.url == url) { -// UrlPreviewState.Loaded(it) -// } else { -// UrlPreviewState.Empty -// } -// } ?: UrlPreviewState.Loading + val default = UrlCachedPreviewer.cache[url]?.let { + if (it.allFetchComplete() && it.url == url) { + UrlPreviewState.Loaded(it) + } else { + UrlPreviewState.Empty + } + } ?: UrlPreviewState.Loading val context = LocalContext.current - var urlPreviewState by remember { mutableStateOf(UrlPreviewState.Loading) } + var urlPreviewState by remember { mutableStateOf(default) } // Doesn't use a viewModel because of viewModel reusing issues (too many UrlPreview are created). LaunchedEffect(url) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt index 304a00ea11..ea18cd3b56 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/components/VideoView.kt @@ -26,14 +26,30 @@ fun VideoView(videoUri: String, onDialog: ((Boolean) -> Unit)? = null) { ExoPlayer.Builder(context).build().apply { repeatMode = Player.REPEAT_MODE_ALL videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING + setMediaSource( + ProgressiveMediaSource.Factory(VideoCache.get(context.applicationContext)).createMediaSource(MediaItem.fromUri(videoUri)) + ) + prepare() + } + } + + val playerView = remember { + StyledPlayerView(context).apply { + player = exoPlayer + layoutParams = FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH + onDialog?.let { innerOnDialog -> + setFullscreenButtonClickListener { + innerOnDialog(it) + } + } } } DisposableEffect(exoPlayer) { - exoPlayer.setMediaSource( - ProgressiveMediaSource.Factory(VideoCache.get(context.applicationContext)).createMediaSource(MediaItem.fromUri(videoUri)) - ) - exoPlayer.prepare() onDispose { exoPlayer.release() } @@ -42,19 +58,7 @@ fun VideoView(videoUri: String, onDialog: ((Boolean) -> Unit)? = null) { AndroidView( modifier = Modifier.fillMaxWidth(), factory = { - StyledPlayerView(context).apply { - player = exoPlayer - layoutParams = FrameLayout.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH - onDialog?.let { innerOnDialog -> - setFullscreenButtonClickListener { - innerOnDialog(it) - } - } - } + playerView } ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index f22e0dddad..685a5a2a0d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -2,19 +2,11 @@ package com.vitorpamplona.amethyst.ui.navigation import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState -import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.rememberPagerState -import com.vitorpamplona.amethyst.ui.dal.GlobalFeedFilter -import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter -import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter -import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.BookmarkListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelScreen @@ -36,16 +28,6 @@ fun AppNavigation( accountViewModel: AccountViewModel, nextPage: String? = null ) { - val accountState by accountViewModel.accountLiveData.observeAsState() - val account = accountState?.account ?: return - - GlobalFeedFilter.account = account - HomeNewThreadFeedFilter.account = account - HomeConversationsFeedFilter.account = account - - val globalFeedViewModel: NostrGlobalFeedViewModel = viewModel() - val homeFeedViewModel: NostrHomeFeedViewModel = viewModel() - val homeRepliesFeedViewModel: NostrHomeRepliesFeedViewModel = viewModel() val homePagerState = rememberPagerState() NavHost(navController, startDestination = Route.Home.route) { @@ -53,7 +35,6 @@ fun AppNavigation( composable(route.route, route.arguments, content = { SearchScreen( accountViewModel = accountViewModel, - feedViewModel = globalFeedViewModel, navController = navController, scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false ) @@ -65,8 +46,6 @@ fun AppNavigation( HomeScreen( accountViewModel = accountViewModel, navController = navController, - homeFeedViewModel = homeFeedViewModel, - repliesFeedViewModel = homeRepliesFeedViewModel, pagerState = homePagerState, scrollToTop = it.arguments?.getBoolean("scrollToTop") ?: false ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt index 7adedfd848..0744d4b1d3 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt @@ -37,7 +37,7 @@ fun CardFeedView(viewModel: CardFeedViewModel, accountViewModel: AccountViewMode val feedState by viewModel.feedContent.collectAsState() var refreshing by remember { mutableStateOf(false) } - val refresh = { refreshing = true; viewModel.refresh(); refreshing = false } + val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false } val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) Box(Modifier.pullRefresh(pullRefreshState)) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index fa80bee9ad..34337677e4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -1,5 +1,6 @@ package com.vitorpamplona.amethyst.ui.screen +import android.util.Log import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.model.LocalCache @@ -12,19 +13,18 @@ import com.vitorpamplona.amethyst.service.model.LnZapEvent import com.vitorpamplona.amethyst.service.model.PrivateDmEvent import com.vitorpamplona.amethyst.service.model.ReactionEvent import com.vitorpamplona.amethyst.service.model.RepostEvent +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.concurrent.atomic.AtomicBoolean +import kotlin.time.ExperimentalTime +import kotlin.time.measureTimedValue class NotificationViewModel : CardFeedViewModel(NotificationFeedFilter) @@ -34,7 +34,7 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { private var lastNotes: List? = null - fun refresh() { + private fun refresh() { val scope = CoroutineScope(Job() + Dispatchers.Default) scope.launch { refreshSuspended() @@ -138,22 +138,18 @@ open class CardFeedViewModel(val dataSource: FeedFilter) : ViewModel() { } } - var handlerWaiting = AtomicBoolean() + @OptIn(ExperimentalTime::class) + private val bundler = BundledUpdate(250, Dispatchers.IO) { + // adds the time to perform the refresh into this delay + // holding off new updates in case of heavy refresh routines. + val (value, elapsed) = measureTimedValue { + refreshSuspended() + } + Log.d("Time", "${this.javaClass.simpleName} Card update $elapsed") + } fun invalidateData() { - if (handlerWaiting.getAndSet(true)) return - - val scope = CoroutineScope(Job() + Dispatchers.Default) - scope.launch { - try { - delay(150) - refresh() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - } + bundler.invalidate() } private val cacheListener: (LocalCacheState) -> Unit = { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt index 8ba6eb059f..169aefd3eb 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt @@ -29,7 +29,7 @@ fun ChatroomFeedView(viewModel: FeedViewModel, accountViewModel: AccountViewMode LaunchedEffect(isRefreshing) { if (isRefreshing) { - viewModel.refresh() + viewModel.invalidateData() isRefreshing = false } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt index 6d4b94e825..a62afa8f1d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt @@ -42,7 +42,7 @@ fun ChatroomListFeedView( val feedState by viewModel.feedContent.collectAsStateWithLifecycle() var refreshing by remember { mutableStateOf(false) } - val refresh = { refreshing = true; viewModel.refresh(); refreshing = false } + val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false } val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) Box(Modifier.pullRefresh(pullRefreshState)) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index 10453c6dfc..dcf3f2a4f5 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -47,7 +47,7 @@ fun FeedView( val feedState by viewModel.feedContent.collectAsState() var refreshing by remember { mutableStateOf(false) } - val refresh = { refreshing = true; viewModel.refresh(); refreshing = false } + val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false } val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) Box(Modifier.pullRefresh(pullRefreshState)) { 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 fd0e45cd7a..a6b2fcdb13 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 @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCacheState import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.dal.BookmarkPrivateFeedFilter import com.vitorpamplona.amethyst.ui.dal.BookmarkPublicFeedFilter import com.vitorpamplona.amethyst.ui.dal.ChannelFeedFilter @@ -24,14 +25,10 @@ import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.concurrent.atomic.AtomicBoolean class NostrChannelFeedViewModel : FeedViewModel(ChannelFeedFilter) class NostrChatRoomFeedViewModel : FeedViewModel(ChatroomFeedFilter) @@ -58,7 +55,7 @@ abstract class FeedViewModel(val localFilter: FeedFilter) : ViewModel() { return localFilter.loadTop() } - fun refresh() { + private fun refresh() { val scope = CoroutineScope(Job() + Dispatchers.Default) scope.launch { refreshSuspended() @@ -94,24 +91,14 @@ abstract class FeedViewModel(val localFilter: FeedFilter) : ViewModel() { } } - private var handlerWaiting = AtomicBoolean() + private val bundler = BundledUpdate(250, Dispatchers.IO) { + // adds the time to perform the refresh into this delay + // holding off new updates in case of heavy refresh routines. + refreshSuspended() + } fun invalidateData() { - if (handlerWaiting.getAndSet(true)) return - - val scope = CoroutineScope(Job() + Dispatchers.Default) - scope.launch { - try { - delay(50) - // adds the time to perform the refresh into this delay - // holding off new updates in case of heavy refresh routines. - refreshSuspended() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - } + bundler.invalidate() } private val cacheListener: (LocalCacheState) -> Unit = { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt index 6981b56317..c13e23f53f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt @@ -31,7 +31,7 @@ fun LnZapFeedView(viewModel: LnZapFeedViewModel, accountViewModel: AccountViewMo val feedState by viewModel.feedContent.collectAsState() var refreshing by remember { mutableStateOf(false) } - val refresh = { refreshing = true; viewModel.refresh(); refreshing = false } + val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false } val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) Box(Modifier.pullRefresh(pullRefreshState)) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt index c76a25966c..9973afab81 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedViewModel.kt @@ -5,19 +5,16 @@ import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCacheState import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileZapsFeedFilter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.concurrent.atomic.AtomicBoolean class NostrUserProfileZapsFeedViewModel : LnZapFeedViewModel(UserProfileZapsFeedFilter) @@ -25,7 +22,7 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter>) : Vi private val _feedContent = MutableStateFlow(LnZapFeedState.Loading) val feedContent = _feedContent.asStateFlow() - fun refresh() { + private fun refresh() { val scope = CoroutineScope(Job() + Dispatchers.Default) scope.launch { refreshSuspended() @@ -61,22 +58,14 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter>) : Vi } } - var handlerWaiting = AtomicBoolean() + private val bundler = BundledUpdate(250, Dispatchers.IO) { + // adds the time to perform the refresh into this delay + // holding off new updates in case of heavy refresh routines. + refreshSuspended() + } fun invalidateData() { - if (handlerWaiting.getAndSet(true)) return - - val scope = CoroutineScope(Job() + Dispatchers.Default) - scope.launch { - try { - delay(50) - refresh() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - } + bundler.invalidate() } private val cacheListener: (LocalCacheState) -> Unit = { 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 8dd8df5f6e..e24453a3ff 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 @@ -26,19 +26,14 @@ import com.vitorpamplona.amethyst.model.RelayInfo import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.ui.actions.NewRelayListView +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.note.RelayCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.concurrent.atomic.AtomicBoolean class RelayFeedViewModel : ViewModel() { val order = compareByDescending { it.lastEvent }.thenByDescending { it.counter }.thenBy { it.url } @@ -50,23 +45,27 @@ class RelayFeedViewModel : ViewModel() { fun refresh() { viewModelScope.launch(Dispatchers.Default) { - val beingUsed = currentUser?.relaysBeingUsed?.values ?: emptyList() - val beingUsedSet = currentUser?.relaysBeingUsed?.keys ?: emptySet() - - val newRelaysFromRecord = currentUser?.latestContactList?.relays()?.entries?.mapNotNull { - if (it.key !in beingUsedSet) { - RelayInfo(it.key, 0, 0) - } else { - null - } - } ?: emptyList() - - val newList = (beingUsed + newRelaysFromRecord).sortedWith(order) - - _feedContent.update { newList } + refreshSuspended() } } + fun refreshSuspended() { + val beingUsed = currentUser?.relaysBeingUsed?.values ?: emptyList() + val beingUsedSet = currentUser?.relaysBeingUsed?.keys ?: emptySet() + + val newRelaysFromRecord = currentUser?.latestContactList?.relays()?.entries?.mapNotNull { + if (it.key !in beingUsedSet) { + RelayInfo(it.key, 0, 0) + } else { + null + } + } ?: emptyList() + + val newList = (beingUsed + newRelaysFromRecord).sortedWith(order) + + _feedContent.update { newList } + } + val listener: (UserState) -> Unit = { invalidateData() } @@ -84,23 +83,14 @@ class RelayFeedViewModel : ViewModel() { currentUser = null } - var handlerWaiting = AtomicBoolean() + private val bundler = BundledUpdate(250, Dispatchers.IO) { + // adds the time to perform the refresh into this delay + // holding off new updates in case of heavy refresh routines. + refreshSuspended() + } - private fun invalidateData() { - if (handlerWaiting.getAndSet(true)) return - - val scope = CoroutineScope(Job() + Dispatchers.Default) - scope.launch { - try { - delay(50) - refresh() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - handlerWaiting.set(false) - } + fun invalidateData() { + bundler.invalidate() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt index 3d5f67be78..b5e05d6f96 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt @@ -79,7 +79,7 @@ fun ThreadFeedView(noteId: String, viewModel: FeedViewModel, accountViewModel: A val listState = rememberLazyListState() var refreshing by remember { mutableStateOf(false) } - val refresh = { refreshing = true; viewModel.refresh(); refreshing = false } + val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false } val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) Box(Modifier.pullRefresh(pullRefreshState)) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt index 916a863da6..79da216b44 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt @@ -31,7 +31,7 @@ fun UserFeedView(viewModel: UserFeedViewModel, accountViewModel: AccountViewMode val feedState by viewModel.feedContent.collectAsState() var refreshing by remember { mutableStateOf(false) } - val refresh = { refreshing = true; viewModel.refresh(); refreshing = false } + val refresh = { refreshing = true; viewModel.invalidateData(); refreshing = false } val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) Box(Modifier.pullRefresh(pullRefreshState)) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index bab4039cdf..500320035a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCacheState import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.HiddenAccountsFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowersFeedFilter @@ -12,14 +13,10 @@ import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowsFeedFilter import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job -import kotlinx.coroutines.NonCancellable -import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import java.util.concurrent.atomic.AtomicBoolean class NostrUserProfileFollowsUserFeedViewModel : UserFeedViewModel(UserProfileFollowsFeedFilter) class NostrUserProfileFollowersUserFeedViewModel : UserFeedViewModel(UserProfileFollowersFeedFilter) @@ -29,7 +26,7 @@ open class UserFeedViewModel(val dataSource: FeedFilter) : ViewModel() { private val _feedContent = MutableStateFlow(UserFeedState.Loading) val feedContent = _feedContent.asStateFlow() - fun refresh() { + private fun refresh() { val scope = CoroutineScope(Job() + Dispatchers.Default) scope.launch { refreshSuspended() @@ -65,23 +62,14 @@ open class UserFeedViewModel(val dataSource: FeedFilter) : ViewModel() { } } - var handlerWaiting = AtomicBoolean() + private val bundler = BundledUpdate(250, Dispatchers.IO) { + // adds the time to perform the refresh into this delay + // holding off new updates in case of heavy refresh routines. + refreshSuspended() + } fun invalidateData() { - if (handlerWaiting.getAndSet(true)) return - - val scope = CoroutineScope(Job() + Dispatchers.Default) - scope.launch { - try { - delay(50) - refresh() - } finally { - withContext(NonCancellable) { - handlerWaiting.set(false) - } - } - handlerWaiting.set(false) - } + bundler.invalidate() } private val cacheListener: (LocalCacheState) -> Unit = { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt index 8520bbab98..547b3c447d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt @@ -97,6 +97,8 @@ fun ChannelScreen( val channelState by NostrChannelDataSource.channel!!.live.observeAsState() val channel = channelState?.channel ?: return + ChannelFeedFilter.loadMessagesBetween(account, channelId) + val feedViewModel: NostrChannelFeedViewModel = viewModel() val lifeCycleOwner = LocalLifecycleOwner.current @@ -214,7 +216,7 @@ fun ChannelScreen( account.sendChannelMessage(channelScreenModel.message.text, channel.idHex, replyTo.value, null) channelScreenModel.message = TextFieldValue("") replyTo.value = null - feedViewModel.refresh() // Don't wait a full second before updating + feedViewModel.invalidateData() // Don't wait a full second before updating }, isActive = channelScreenModel.message.text.isNotBlank() && !channelScreenModel.isUploadingImage, modifier = Modifier.padding(end = 10.dp) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt index 537cab9dbb..6406aced94 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt @@ -141,7 +141,7 @@ fun TabKnown( LaunchedEffect(accountViewModel) { NostrChatroomListDataSource.account = account NostrChatroomListDataSource.start() - feedViewModel.refresh() + feedViewModel.invalidateData() } val lifeCycleOwner = LocalLifecycleOwner.current @@ -150,7 +150,7 @@ fun TabKnown( if (event == Lifecycle.Event.ON_RESUME) { NostrChatroomListDataSource.account = account NostrChatroomListDataSource.start() - feedViewModel.refresh() + feedViewModel.invalidateData() } } @@ -184,7 +184,7 @@ fun TabNew( LaunchedEffect(accountViewModel) { NostrChatroomListDataSource.account = account NostrChatroomListDataSource.start() - feedViewModel.refresh() + feedViewModel.invalidateData() } val lifeCycleOwner = LocalLifecycleOwner.current @@ -193,7 +193,7 @@ fun TabNew( if (event == Lifecycle.Event.ON_RESUME) { NostrChatroomListDataSource.account = account NostrChatroomListDataSource.start() - feedViewModel.refresh() + feedViewModel.invalidateData() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt index 69b4d96ab3..e03561acc4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt @@ -77,7 +77,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr val lifeCycleOwner = LocalLifecycleOwner.current LaunchedEffect(userId) { - feedViewModel.refresh() + feedViewModel.invalidateData() chatRoomScreenModel.imageUploadingError.collect { error -> Toast.makeText(context, error, Toast.LENGTH_SHORT).show() } @@ -88,7 +88,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr if (event == Lifecycle.Event.ON_RESUME) { println("Private Message Start") NostrChatroomDataSource.start() - feedViewModel.refresh() + feedViewModel.invalidateData() } if (event == Lifecycle.Event.ON_PAUSE) { println("Private Message Stop") @@ -180,7 +180,7 @@ fun ChatroomScreen(userId: String?, accountViewModel: AccountViewModel, navContr account.sendPrivateMessage(chatRoomScreenModel.message.text, userId, replyTo.value) chatRoomScreenModel.message = TextFieldValue("") replyTo.value = null - feedViewModel.refresh() // Don't wait a full second before updating + feedViewModel.invalidateData() // Don't wait a full second before updating }, isActive = chatRoomScreenModel.message.text.isNotBlank() && !chatRoomScreenModel.isUploadingImage, modifier = Modifier.padding(end = 10.dp) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt index addf0827e4..f81a10fd62 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HashtagScreen.kt @@ -39,6 +39,7 @@ fun HashtagScreen(tag: String?, accountViewModel: AccountViewModel, navControlle val lifeCycleOwner = LocalLifecycleOwner.current if (tag != null) { + HashtagFeedFilter.loadHashtag(account, tag) val feedViewModel: NostrHashtagFeedViewModel = viewModel() LaunchedEffect(tag) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt index 45485eb1b8..5733c3aab9 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt @@ -18,6 +18,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager @@ -39,20 +40,24 @@ import kotlinx.coroutines.launch fun HomeScreen( accountViewModel: AccountViewModel, navController: NavController, - homeFeedViewModel: NostrHomeFeedViewModel, - repliesFeedViewModel: NostrHomeRepliesFeedViewModel, pagerState: PagerState, scrollToTop: Boolean = false ) { val coroutineScope = rememberCoroutineScope() val account = accountViewModel.accountLiveData.value?.account ?: return + HomeNewThreadFeedFilter.account = account + HomeConversationsFeedFilter.account = account + + val homeFeedViewModel: NostrHomeFeedViewModel = viewModel() + val repliesFeedViewModel: NostrHomeRepliesFeedViewModel = viewModel() + LaunchedEffect(accountViewModel) { HomeNewThreadFeedFilter.account = account HomeConversationsFeedFilter.account = account NostrHomeDataSource.resetFilters() - homeFeedViewModel.refresh() - repliesFeedViewModel.refresh() + homeFeedViewModel.invalidateData() + repliesFeedViewModel.invalidateData() } val lifeCycleOwner = LocalLifecycleOwner.current @@ -60,8 +65,8 @@ fun HomeScreen( val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { NostrHomeDataSource.resetFilters() - homeFeedViewModel.refresh() - repliesFeedViewModel.refresh() + homeFeedViewModel.invalidateData() + repliesFeedViewModel.invalidateData() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index 55bb17ebaf..92c5ce1290 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -29,14 +29,14 @@ fun NotificationScreen(accountViewModel: AccountViewModel, navController: NavCon val feedViewModel: NotificationViewModel = viewModel() LaunchedEffect(accountViewModel) { - feedViewModel.refresh() + feedViewModel.invalidateData() } val lifeCycleOwner = LocalLifecycleOwner.current DisposableEffect(accountViewModel) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { - feedViewModel.refresh() + feedViewModel.invalidateData() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 5908a40036..03a1b40d3a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -681,7 +681,7 @@ fun TabNotesNewThreads(accountViewModel: AccountViewModel, navController: NavCon val feedViewModel: NostrUserProfileNewThreadsFeedViewModel = viewModel() LaunchedEffect(Unit) { - feedViewModel.refresh() + feedViewModel.invalidateData() } Column(Modifier.fillMaxHeight()) { @@ -701,7 +701,7 @@ fun TabNotesConversations(accountViewModel: AccountViewModel, navController: Nav val feedViewModel: NostrUserProfileConversationsFeedViewModel = viewModel() LaunchedEffect(Unit) { - feedViewModel.refresh() + feedViewModel.invalidateData() } Column(Modifier.fillMaxHeight()) { @@ -722,7 +722,7 @@ fun TabBookmarks(baseUser: User, accountViewModel: AccountViewModel, navControll val feedViewModel: NostrUserProfileBookmarksFeedViewModel = viewModel() LaunchedEffect(userState) { - feedViewModel.refresh() + feedViewModel.invalidateData() } Column(Modifier.fillMaxHeight()) { diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt index 45fc58e819..57e69750f6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/SearchScreen.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account @@ -62,7 +63,7 @@ import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.note.UserPicture import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.screen.FeedView -import com.vitorpamplona.amethyst.ui.screen.FeedViewModel +import com.vitorpamplona.amethyst.ui.screen.NostrGlobalFeedViewModel import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview @@ -79,17 +80,20 @@ import kotlinx.coroutines.channels.Channel as CoroutineChannel @Composable fun SearchScreen( accountViewModel: AccountViewModel, - feedViewModel: FeedViewModel, navController: NavController, scrollToTop: Boolean = false ) { val lifeCycleOwner = LocalLifecycleOwner.current val account = accountViewModel.accountLiveData.value?.account ?: return + GlobalFeedFilter.account = account + + val feedViewModel: NostrGlobalFeedViewModel = viewModel() + LaunchedEffect(accountViewModel) { GlobalFeedFilter.account = account NostrGlobalDataSource.resetFilters() - feedViewModel.refresh() + feedViewModel.invalidateData() } DisposableEffect(accountViewModel) { @@ -98,7 +102,7 @@ fun SearchScreen( println("Global Start") NostrGlobalDataSource.start() NostrSearchEventOrUserDataSource.start() - feedViewModel.refresh() + feedViewModel.invalidateData() } if (event == Lifecycle.Event.ON_PAUSE) { println("Global Stop") 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 c984de4be3..2d5d6aa537 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 @@ -27,11 +27,12 @@ fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, navControl val lifeCycleOwner = LocalLifecycleOwner.current if (account != null && noteId != null) { + ThreadFeedFilter.loadThread(noteId) val feedViewModel: NostrThreadFeedViewModel = viewModel() LaunchedEffect(noteId) { - ThreadFeedFilter.loadThread(noteId) NostrThreadDataSource.loadThread(noteId) + ThreadFeedFilter.loadThread(noteId) feedViewModel.invalidateData() } @@ -39,9 +40,9 @@ fun ThreadScreen(noteId: String?, accountViewModel: AccountViewModel, navControl val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { println("Thread Start") - ThreadFeedFilter.loadThread(noteId) NostrThreadDataSource.loadThread(noteId) NostrThreadDataSource.start() + ThreadFeedFilter.loadThread(noteId) feedViewModel.invalidateData() } if (event == Lifecycle.Event.ON_PAUSE) {