Turn FollowList into a ViewModel and a LocalCache LiveData into a SharedFlow (it was killing some coroutines that were supposed to finish)

This commit is contained in:
Vitor Pamplona
2023-05-13 12:49:48 -04:00
parent 3e69b81d81
commit 6842d12b39
11 changed files with 167 additions and 101 deletions
@@ -2,7 +2,6 @@ package com.vitorpamplona.amethyst.model
import android.util.Log import android.util.Log
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.lifecycle.LiveData
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.vitorpamplona.amethyst.Amethyst import com.vitorpamplona.amethyst.Amethyst
@@ -11,6 +10,8 @@ import com.vitorpamplona.amethyst.service.relays.Relay
import com.vitorpamplona.amethyst.ui.components.BundledInsert import com.vitorpamplona.amethyst.ui.components.BundledInsert
import fr.acinq.secp256k1.Hex import fr.acinq.secp256k1.Hex
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import nostr.postr.toNpub import nostr.postr.toNpub
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.File import java.io.File
@@ -969,16 +970,16 @@ object LocalCache {
} }
} }
class LocalCacheLiveData : LiveData<Set<Note>>(setOf<Note>()) { class LocalCacheLiveData {
private val _newEventBundles = MutableSharedFlow<Set<Note>>()
val newEventBundles = _newEventBundles.asSharedFlow() // read-only public view
// Refreshes observers in batches. // Refreshes observers in batches.
private val bundler = BundledInsert<Note>(300, Dispatchers.IO) private val bundler = BundledInsert<Note>(300, Dispatchers.IO)
fun invalidateData(newNote: Note) { fun invalidateData(newNote: Note) {
bundler.invalidateList(newNote) { bundledNewNotes -> bundler.invalidateList(newNote) { bundledNewNotes ->
if (hasActiveObservers()) { _newEventBundles.emit(bundledNewNotes)
postValue(bundledNewNotes)
}
} }
} }
} }
@@ -137,9 +137,6 @@ private fun RenderSeach(
val onlineSearch = NostrSearchEventOrUserDataSource val onlineSearch = NostrSearchEventOrUserDataSource
val dbState = LocalCache.live.observeAsState()
val db = dbState.value ?: return
val lifeCycleOwner = LocalLifecycleOwner.current val lifeCycleOwner = LocalLifecycleOwner.current
// Create a channel for processing search queries. // Create a channel for processing search queries.
@@ -147,10 +144,12 @@ private fun RenderSeach(
Channel<String>(Channel.CONFLATED) Channel<String>(Channel.CONFLATED)
} }
LaunchedEffect(db) { LaunchedEffect(Unit) {
withContext(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
if (searchBarViewModel.isSearching()) { LocalCache.live.newEventBundles.collect {
searchBarViewModel.invalidateData() if (searchBarViewModel.isSearching()) {
searchBarViewModel.invalidateData()
}
} }
} }
} }
@@ -8,8 +8,8 @@ import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
/** /**
* This class is designed to have a waiting time between two calls of invalidate * This class is designed to have a waiting time between two calls of invalidate
@@ -54,23 +54,26 @@ class BundledInsert<T>(
val dispatcher: CoroutineDispatcher = Dispatchers.Default val dispatcher: CoroutineDispatcher = Dispatchers.Default
) { ) {
private var onlyOneInBlock = AtomicBoolean() private var onlyOneInBlock = AtomicBoolean()
private var atomicSet = AtomicReference<Set<T>>(setOf<T>()) private var queue = LinkedBlockingQueue<T>()
fun invalidateList(newObject: T, onUpdate: (Set<T>) -> Unit) {
atomicSet.updateAndGet() {
it + newObject
}
fun invalidateList(newObject: T, onUpdate: suspend (Set<T>) -> Unit) {
queue.put(newObject)
if (onlyOneInBlock.getAndSet(true)) { if (onlyOneInBlock.getAndSet(true)) {
return return
} }
val scope = CoroutineScope(Job() + dispatcher) val scope = CoroutineScope(Job() + dispatcher)
scope.launch { scope.launch(Dispatchers.IO) {
try { try {
onUpdate(atomicSet.getAndSet(emptySet())) val mySet = mutableSetOf<T>()
queue.drainTo(mySet)
onUpdate(mySet)
delay(delay) delay(delay)
onUpdate(atomicSet.getAndSet(emptySet()))
mySet.clear()
queue.drainTo(mySet)
onUpdate(mySet)
} finally { } finally {
withContext(NonCancellable) { withContext(NonCancellable) {
onlyOneInBlock.set(false) onlyOneInBlock.set(false)
@@ -41,7 +41,6 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.time.ExperimentalTime
val bottomNavigationItems = listOf( val bottomNavigationItems = listOf(
Route.Home, Route.Home,
@@ -136,9 +135,10 @@ fun AppBottomBar(navController: NavHostController, accountViewModel: AccountView
} }
} }
@OptIn(ExperimentalTime::class)
@Composable @Composable
private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: AccountViewModel) { private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: AccountViewModel) {
val scope = rememberCoroutineScope()
Box(Modifier.size(if ("Home" == route.base) 25.dp else 23.dp)) { Box(Modifier.size(if ("Home" == route.base) 25.dp else 23.dp)) {
Icon( Icon(
painter = painterResource(id = route.icon), painter = painterResource(id = route.icon),
@@ -150,15 +150,10 @@ private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: Ac
val accountState by accountViewModel.accountLiveData.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val account = accountState?.account ?: return val account = accountState?.account ?: return
// Notification
val dbState = LocalCache.live.observeAsState()
val db = dbState.value ?: return
val notifState = NotificationCache.live.observeAsState() val notifState = NotificationCache.live.observeAsState()
val notif = notifState.value ?: return val notif = notifState.value ?: return
var hasNewItems by remember { mutableStateOf<Boolean>(false) } var hasNewItems by remember { mutableStateOf<Boolean>(false) }
val scope = rememberCoroutineScope()
LaunchedEffect(key1 = notif) { LaunchedEffect(key1 = notif) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
@@ -166,9 +161,11 @@ private fun NotifiableIcon(route: Route, selected: Boolean, accountViewModel: Ac
} }
} }
LaunchedEffect(key1 = db) { LaunchedEffect(Unit) {
scope.launch(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
hasNewItems = route.hasNewItems(account, notif.cache, db) LocalCache.live.newEventBundles.collect {
hasNewItems = route.hasNewItems(account, notif.cache, it)
}
} }
} }
@@ -24,7 +24,6 @@ import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.livedata.observeAsState
@@ -40,6 +39,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import coil.Coil import coil.Coil
@@ -71,43 +72,45 @@ import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImageProxy
import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel import com.vitorpamplona.amethyst.ui.screen.RelayPoolViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@Composable @Composable
fun AppTopBar(navController: NavHostController, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { fun AppTopBar(followLists: FollowListViewModel, navController: NavHostController, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
when (currentRoute(navController)?.substringBefore("?")) { when (currentRoute(navController)?.substringBefore("?")) {
// Route.Profile.route -> TopBarWithBackButton(navController) // Route.Profile.route -> TopBarWithBackButton(navController)
Route.Home.base -> HomeTopBar(scaffoldState, accountViewModel) Route.Home.base -> HomeTopBar(followLists, scaffoldState, accountViewModel)
Route.Video.base -> StoriesTopBar(scaffoldState, accountViewModel) Route.Video.base -> StoriesTopBar(followLists, scaffoldState, accountViewModel)
Route.Notification.base -> NotificationTopBar(scaffoldState, accountViewModel) Route.Notification.base -> NotificationTopBar(followLists, scaffoldState, accountViewModel)
else -> MainTopBar(scaffoldState, accountViewModel) else -> MainTopBar(scaffoldState, accountViewModel)
} }
} }
@Composable @Composable
fun StoriesTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { fun StoriesTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
GenericTopBar(scaffoldState, accountViewModel) { account -> GenericTopBar(scaffoldState, accountViewModel) { account ->
FollowList(account.defaultStoriesFollowList, account.userProfile(), true) { listName -> FollowList(followLists, account.defaultStoriesFollowList, account.userProfile(), true) { listName ->
account.changeDefaultStoriesFollowList(listName) account.changeDefaultStoriesFollowList(listName)
} }
} }
} }
@Composable @Composable
fun HomeTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { fun HomeTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
GenericTopBar(scaffoldState, accountViewModel) { account -> GenericTopBar(scaffoldState, accountViewModel) { account ->
FollowList(account.defaultHomeFollowList, account.userProfile(), false) { listName -> FollowList(followLists, account.defaultHomeFollowList, account.userProfile(), false) { listName ->
account.changeDefaultHomeFollowList(listName) account.changeDefaultHomeFollowList(listName)
} }
} }
} }
@Composable @Composable
fun NotificationTopBar(scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) { fun NotificationTopBar(followLists: FollowListViewModel, scaffoldState: ScaffoldState, accountViewModel: AccountViewModel) {
GenericTopBar(scaffoldState, accountViewModel) { account -> GenericTopBar(scaffoldState, accountViewModel) { account ->
FollowList(account.defaultNotificationFollowList, account.userProfile(), true) { listName -> FollowList(followLists, account.defaultNotificationFollowList, account.userProfile(), true) { listName ->
account.changeDefaultNotificationFollowList(listName) account.changeDefaultNotificationFollowList(listName)
} }
} }
@@ -237,30 +240,19 @@ private fun LoggedInUserPictureDrawer(
} }
@Composable @Composable
fun FollowList(listName: String, loggedIn: User, withGlobal: Boolean, onChange: (String) -> Unit) { fun FollowList(followListsModel: FollowListViewModel, listName: String, loggedIn: User, withGlobal: Boolean, onChange: (String) -> Unit) {
// Notification
val dbState = LocalCache.live.observeAsState()
val db = dbState.value ?: return
val kind3Follow = Pair(KIND3_FOLLOWS, stringResource(id = R.string.follow_list_kind3follows)) val kind3Follow = Pair(KIND3_FOLLOWS, stringResource(id = R.string.follow_list_kind3follows))
val globalFollow = Pair(GLOBAL_FOLLOWS, stringResource(id = R.string.follow_list_global)) val globalFollow = Pair(GLOBAL_FOLLOWS, stringResource(id = R.string.follow_list_global))
val defaultOptions = if (withGlobal) listOf(kind3Follow, globalFollow) else listOf(kind3Follow) val defaultOptions = if (withGlobal) listOf(kind3Follow, globalFollow) else listOf(kind3Follow)
var followLists by remember { mutableStateOf(defaultOptions) } val followLists = remember(followListsModel.followLists) {
val followNames = remember { derivedStateOf { followLists.map { it.second } } } (defaultOptions + followListsModel.followLists)
}
LaunchedEffect(key1 = db) { val followNames = remember(followLists) {
withContext(Dispatchers.IO) { derivedStateOf {
followLists = defaultOptions + LocalCache.addressables.mapNotNull { followLists.map { it.second }
val event = (it.value.event as? PeopleListEvent)
// Has to have an list
if (event != null && event.pubKey == loggedIn.pubkeyHex && (event.tags.size > 1 || event.content.length > 50)) {
Pair(event.dTag(), event.dTag())
} else {
null
}
}.sortedBy { it.second }
} }
} }
@@ -273,6 +265,62 @@ fun FollowList(listName: String, loggedIn: User, withGlobal: Boolean, onChange:
) )
} }
class FollowListViewModel : ViewModel() {
var followLists by mutableStateOf<List<Pair<String, String>>>(emptyList())
var account: Account? = null
fun load(account: Account?) {
this.account = account
refresh()
}
fun refresh() {
val scope = CoroutineScope(Job() + Dispatchers.Default)
scope.launch {
refreshFollows()
}
}
private suspend fun refreshFollows() {
val myAccount = account ?: return
val newFollowLists = LocalCache.addressables.mapNotNull {
val event = (it.value.event as? PeopleListEvent)
// Has to have an list
if (event != null && event.pubKey == myAccount.userProfile().pubkeyHex && (event.tags.size > 1 || event.content.length > 50)) {
Pair(event.dTag(), event.dTag())
} else {
null
}
}.sortedBy { it.second }
withContext(Dispatchers.Main) {
if (followLists != newFollowLists) {
followLists = newFollowLists
}
}
}
var collectorJob: Job? = null
init {
collectorJob = viewModelScope.launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect { newNotes ->
newNotes.forEach {
if (it.event is PeopleListEvent) {
refresh()
}
}
}
}
}
override fun onCleared() {
collectorJob?.cancel()
super.onCleared()
}
}
@Composable @Composable
fun SimpleTextSpinner( fun SimpleTextSpinner(
placeholder: String, placeholder: String,
@@ -3,6 +3,7 @@ package com.vitorpamplona.amethyst.ui.screen
import android.util.Log import android.util.Log
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
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
@@ -222,17 +223,19 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
} }
} }
private val cacheListener: (Set<Note>) -> Unit = { newNotes -> var collectorJob: Job? = null
if (localFilter is AdditiveFeedFilter && _feedContent.value is CardFeedState.Loaded) {
invalidateInsertData(newNotes)
} else {
// Refresh Everything
invalidateData()
}
}
init { init {
LocalCache.live.observeForever(cacheListener) collectorJob = viewModelScope.launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect { newNotes ->
if (localFilter is AdditiveFeedFilter && _feedContent.value is CardFeedState.Loaded) {
invalidateInsertData(newNotes)
} else {
// Refresh Everything
invalidateData()
}
}
}
} }
fun clear() { fun clear() {
@@ -242,7 +245,7 @@ open class CardFeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
override fun onCleared() { override fun onCleared() {
clear() clear()
LocalCache.live.removeObserver(cacheListener) collectorJob?.cancel()
super.onCleared() super.onCleared()
} }
} }
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.BundledInsert import com.vitorpamplona.amethyst.ui.components.BundledInsert
@@ -124,21 +125,23 @@ abstract class FeedViewModel(val localFilter: FeedFilter<Note>) : ViewModel() {
} }
} }
private val cacheListener: (Set<Note>) -> Unit = { newNotes -> var collectorJob: Job? = null
if (localFilter is AdditiveFeedFilter && _feedContent.value is FeedState.Loaded) {
invalidateInsertData(newNotes) init {
} else { collectorJob = viewModelScope.launch(Dispatchers.IO) {
// Refresh Everything LocalCache.live.newEventBundles.collect { newNotes ->
invalidateData() if (localFilter is AdditiveFeedFilter && _feedContent.value is FeedState.Loaded) {
invalidateInsertData(newNotes)
} else {
// Refresh Everything
invalidateData()
}
}
} }
} }
init {
LocalCache.live.observeForever(cacheListener)
}
override fun onCleared() { override fun onCleared() {
LocalCache.live.removeObserver(cacheListener) collectorJob?.cancel()
super.onCleared() super.onCleared()
} }
} }
@@ -2,6 +2,7 @@ package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.components.BundledUpdate
@@ -67,16 +68,18 @@ open class LnZapFeedViewModel(val dataSource: FeedFilter<Pair<Note, Note>>) : Vi
bundler.invalidate() bundler.invalidate()
} }
private val cacheListener: (Set<Note>) -> Unit = { newNotes -> var collectorJob: Job? = null
invalidateData()
}
init { init {
LocalCache.live.observeForever(cacheListener) collectorJob = viewModelScope.launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect { newNotes ->
invalidateData()
}
}
} }
override fun onCleared() { override fun onCleared() {
LocalCache.live.removeObserver(cacheListener) collectorJob?.cancel()
super.onCleared() super.onCleared()
} }
} }
@@ -2,8 +2,8 @@ package com.vitorpamplona.amethyst.ui.screen
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.ui.components.BundledUpdate import com.vitorpamplona.amethyst.ui.components.BundledUpdate
import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.FeedFilter
@@ -72,16 +72,18 @@ open class UserFeedViewModel(val dataSource: FeedFilter<User>) : ViewModel() {
bundler.invalidate() bundler.invalidate()
} }
private val cacheListener: (Set<Note>) -> Unit = { newNotes -> var collectorJob: Job? = null
invalidateData()
}
init { init {
LocalCache.live.observeForever(cacheListener) collectorJob = viewModelScope.launch(Dispatchers.IO) {
LocalCache.live.newEventBundles.collect { newNotes ->
invalidateData()
}
}
} }
override fun onCleared() { override fun onCleared() {
LocalCache.live.removeObserver(cacheListener) collectorJob?.cancel()
super.onCleared() super.onCleared()
} }
} }
@@ -20,8 +20,11 @@ import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.vitorpamplona.amethyst.ui.buttons.ChannelFabColumn import com.vitorpamplona.amethyst.ui.buttons.ChannelFabColumn
@@ -50,6 +53,12 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
skipHalfExpanded = true skipHalfExpanded = true
) )
val accountState by accountViewModel.accountLiveData.observeAsState()
val account = remember(accountState) { accountState?.account }
val followLists: FollowListViewModel = viewModel()
followLists.load(account)
ModalBottomSheetLayout( ModalBottomSheetLayout(
sheetState = sheetState, sheetState = sheetState,
sheetContent = { sheetContent = {
@@ -64,7 +73,7 @@ fun MainScreen(accountViewModel: AccountViewModel, accountStateViewModel: Accoun
AppBottomBar(navController, accountViewModel) AppBottomBar(navController, accountViewModel)
}, },
topBar = { topBar = {
AppTopBar(navController, scaffoldState, accountViewModel) AppTopBar(followLists, navController, scaffoldState, accountViewModel)
}, },
drawerContent = { drawerContent = {
DrawerContent(navController, scaffoldState, sheetState, accountViewModel) DrawerContent(navController, scaffoldState, sheetState, accountViewModel)
@@ -191,19 +191,17 @@ private fun SearchBar(accountViewModel: AccountViewModel, navController: NavCont
val onlineSearch = NostrSearchEventOrUserDataSource val onlineSearch = NostrSearchEventOrUserDataSource
val dbState = LocalCache.live.observeAsState()
val db = dbState.value ?: return
// Create a channel for processing search queries. // Create a channel for processing search queries.
val searchTextChanges = remember { val searchTextChanges = remember {
CoroutineChannel<String>(CoroutineChannel.CONFLATED) CoroutineChannel<String>(CoroutineChannel.CONFLATED)
} }
LaunchedEffect(db) { LaunchedEffect(Unit) {
withContext(Dispatchers.IO) { scope.launch(Dispatchers.IO) {
if (searchBarViewModel.isSearching()) { LocalCache.live.newEventBundles.collect {
println("Search Active") if (searchBarViewModel.isSearching()) {
searchBarViewModel.invalidateData() searchBarViewModel.invalidateData()
}
} }
} }
} }