mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-09-29 07:22:54 +02:00
Moves has-new-item bottom navigation computations to use the new feed states and last read database in flows.
This commit is contained in:
@@ -194,7 +194,7 @@ fun AddNotifIconIfNeeded(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val flow = accountViewModel.notificationDots.hasNewItems[route] ?: return
|
val flow = accountViewModel.hasNewItems[route] ?: return
|
||||||
val hasNewItems by flow.collectAsStateWithLifecycle()
|
val hasNewItems by flow.collectAsStateWithLifecycle()
|
||||||
if (hasNewItems) {
|
if (hasNewItems) {
|
||||||
NotificationDotIcon(modifier)
|
NotificationDotIcon(modifier)
|
||||||
|
@@ -32,20 +32,10 @@ import androidx.navigation.NavHostController
|
|||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
|
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
import com.vitorpamplona.amethyst.ui.theme.Size20dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size23dp
|
import com.vitorpamplona.amethyst.ui.theme.Size23dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size24dp
|
import com.vitorpamplona.amethyst.ui.theme.Size24dp
|
||||||
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
import com.vitorpamplona.amethyst.ui.theme.Size25dp
|
||||||
import com.vitorpamplona.quartz.events.ChatroomKeyable
|
|
||||||
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
|
||||||
import com.vitorpamplona.quartz.events.RepostEvent
|
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
@@ -58,9 +48,6 @@ sealed class Route(
|
|||||||
val notifSize: Modifier = Modifier.size(Size23dp),
|
val notifSize: Modifier = Modifier.size(Size23dp),
|
||||||
val iconSize: Modifier = Modifier.size(Size20dp),
|
val iconSize: Modifier = Modifier.size(Size20dp),
|
||||||
val contentDescriptor: Int = R.string.route,
|
val contentDescriptor: Int = R.string.route,
|
||||||
val hasNewItems: (Account, Set<com.vitorpamplona.amethyst.model.Note>) -> Boolean = { _, _ ->
|
|
||||||
false
|
|
||||||
},
|
|
||||||
val arguments: ImmutableList<NamedNavArgument> = persistentListOf(),
|
val arguments: ImmutableList<NamedNavArgument> = persistentListOf(),
|
||||||
) {
|
) {
|
||||||
object Home :
|
object Home :
|
||||||
@@ -78,9 +65,6 @@ sealed class Route(
|
|||||||
},
|
},
|
||||||
).toImmutableList(),
|
).toImmutableList(),
|
||||||
contentDescriptor = R.string.route_home,
|
contentDescriptor = R.string.route_home,
|
||||||
hasNewItems = { accountViewModel, newNotes ->
|
|
||||||
HomeLatestItem.hasNewItems(accountViewModel, newNotes)
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
object Global :
|
object Global :
|
||||||
@@ -118,9 +102,6 @@ sealed class Route(
|
|||||||
Route(
|
Route(
|
||||||
route = "Notification",
|
route = "Notification",
|
||||||
icon = R.drawable.ic_notifications,
|
icon = R.drawable.ic_notifications,
|
||||||
hasNewItems = { accountViewModel, newNotes ->
|
|
||||||
NotificationLatestItem.hasNewItems(accountViewModel, newNotes)
|
|
||||||
},
|
|
||||||
contentDescriptor = R.string.route_notifications,
|
contentDescriptor = R.string.route_notifications,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -128,9 +109,6 @@ sealed class Route(
|
|||||||
Route(
|
Route(
|
||||||
route = "Message",
|
route = "Message",
|
||||||
icon = R.drawable.ic_dm,
|
icon = R.drawable.ic_dm,
|
||||||
hasNewItems = { accountViewModel, newNotes ->
|
|
||||||
MessagesLatestItem.hasNewItems(accountViewModel, newNotes)
|
|
||||||
},
|
|
||||||
contentDescriptor = R.string.route_messages,
|
contentDescriptor = R.string.route_messages,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -241,138 +219,6 @@ sealed class Route(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
open class LatestItem {
|
|
||||||
var newestItemPerAccount: Map<String, Note?> = mapOf()
|
|
||||||
|
|
||||||
fun getNewestItem(account: Account): Note? = newestItemPerAccount[account.userProfile().pubkeyHex]
|
|
||||||
|
|
||||||
fun clearNewestItem(account: Account) {
|
|
||||||
val userHex = account.userProfile().pubkeyHex
|
|
||||||
if (newestItemPerAccount.contains(userHex)) {
|
|
||||||
newestItemPerAccount = newestItemPerAccount - userHex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateNewestItem(
|
|
||||||
newNotes: Set<Note>,
|
|
||||||
account: Account,
|
|
||||||
filter: AdditiveFeedFilter<Note>,
|
|
||||||
): Note? {
|
|
||||||
val newestItem = newestItemPerAccount[account.userProfile().pubkeyHex]
|
|
||||||
|
|
||||||
// Block list got updated
|
|
||||||
val newNewest =
|
|
||||||
if (newestItem == null || !account.isAcceptable(newestItem)) {
|
|
||||||
filterMore(filter.feed(), account).firstOrNull { it.createdAt() != null && account.isAcceptable(it) }
|
|
||||||
} else {
|
|
||||||
filter
|
|
||||||
.sort(
|
|
||||||
filterMore(filter.applyFilter(newNotes), account) + newestItem,
|
|
||||||
).firstOrNull { it.createdAt() != null && account.isAcceptable(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
newestItemPerAccount = newestItemPerAccount + Pair(account.userProfile().pubkeyHex, newNewest)
|
|
||||||
|
|
||||||
return newestItemPerAccount[account.userProfile().pubkeyHex]
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun filterMore(
|
|
||||||
newItems: Set<Note>,
|
|
||||||
account: Account,
|
|
||||||
): Set<Note> = newItems
|
|
||||||
|
|
||||||
open fun filterMore(
|
|
||||||
newItems: List<Note>,
|
|
||||||
account: Account,
|
|
||||||
): List<Note> = newItems
|
|
||||||
}
|
|
||||||
|
|
||||||
object HomeLatestItem : LatestItem() {
|
|
||||||
fun hasNewItems(
|
|
||||||
account: Account,
|
|
||||||
newNotes: Set<Note>,
|
|
||||||
): Boolean {
|
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
val lastTime = account.loadLastRead("HomeFollows")
|
|
||||||
|
|
||||||
val newestItem = updateNewestItem(newNotes, account, HomeNewThreadFeedFilter(account))
|
|
||||||
|
|
||||||
return (newestItem?.createdAt() ?: 0) > lastTime
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun filterMore(
|
|
||||||
newItems: Set<Note>,
|
|
||||||
account: Account,
|
|
||||||
): Set<Note> {
|
|
||||||
// removes reposts from the dot notifications.
|
|
||||||
return newItems.filter { it.event !is GenericRepostEvent && it.event !is RepostEvent }.toSet()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object NotificationLatestItem : LatestItem() {
|
|
||||||
fun hasNewItems(
|
|
||||||
account: Account,
|
|
||||||
newNotes: Set<Note>,
|
|
||||||
): Boolean {
|
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
val lastTime = account.loadLastRead("Notification")
|
|
||||||
|
|
||||||
val newestItem = updateNewestItem(newNotes, account, NotificationFeedFilter(account))
|
|
||||||
|
|
||||||
return (newestItem?.createdAt() ?: 0) > lastTime
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object MessagesLatestItem : LatestItem() {
|
|
||||||
fun hasNewItems(
|
|
||||||
account: Account,
|
|
||||||
newNotes: Set<Note>,
|
|
||||||
): Boolean {
|
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
// Checks if the current newest item is still unread.
|
|
||||||
// If so, there is no need to check anything else
|
|
||||||
if (isNew(getNewestItem(account), account)) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
clearNewestItem(account)
|
|
||||||
|
|
||||||
// gets the newest of the unread items
|
|
||||||
val newestItem = updateNewestItem(newNotes, account, ChatroomListKnownFeedFilter(account))
|
|
||||||
|
|
||||||
return isNew(newestItem, account)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isNew(
|
|
||||||
it: Note?,
|
|
||||||
account: Account,
|
|
||||||
): Boolean {
|
|
||||||
if (it == null) return false
|
|
||||||
|
|
||||||
val currentUser = account.userProfile().pubkeyHex
|
|
||||||
val room = (it.event as? ChatroomKeyable)?.chatroomKey(currentUser)
|
|
||||||
return if (room != null) {
|
|
||||||
val lastRead = account.loadLastRead("Room/${room.hashCode()}")
|
|
||||||
(it.createdAt() ?: 0) > lastRead
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun filterMore(
|
|
||||||
newItems: Set<Note>,
|
|
||||||
account: Account,
|
|
||||||
): Set<Note> = newItems.filter { isNew(it, account) }.toSet()
|
|
||||||
|
|
||||||
override fun filterMore(
|
|
||||||
newItems: List<Note>,
|
|
||||||
account: Account,
|
|
||||||
): List<Note> = newItems.filter { isNew(it, account) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getRouteWithArguments(navController: NavHostController): String? {
|
fun getRouteWithArguments(navController: NavHostController): String? {
|
||||||
val currentEntry = navController.currentBackStackEntry ?: return null
|
val currentEntry = navController.currentBackStackEntry ?: return null
|
||||||
return getRouteWithArguments(currentEntry.destination, currentEntry.arguments)
|
return getRouteWithArguments(currentEntry.destination, currentEntry.arguments)
|
||||||
|
@@ -58,13 +58,14 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
|||||||
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
import com.vitorpamplona.amethyst.service.lnurl.LightningAddressResolver
|
||||||
import com.vitorpamplona.amethyst.ui.actions.Dao
|
import com.vitorpamplona.amethyst.ui.actions.Dao
|
||||||
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
import com.vitorpamplona.amethyst.ui.components.UrlPreviewState
|
||||||
|
import com.vitorpamplona.amethyst.ui.feeds.FeedState
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.Route
|
import com.vitorpamplona.amethyst.ui.navigation.Route
|
||||||
import com.vitorpamplona.amethyst.ui.navigation.bottomNavigationItems
|
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
|
import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification
|
||||||
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
|
import com.vitorpamplona.amethyst.ui.note.ZapraiserStatus
|
||||||
import com.vitorpamplona.amethyst.ui.note.showAmount
|
import com.vitorpamplona.amethyst.ui.note.showAmount
|
||||||
import com.vitorpamplona.amethyst.ui.screen.SettingsState
|
import com.vitorpamplona.amethyst.ui.screen.SettingsState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel
|
||||||
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CardFeedState
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CombinedZap
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.CombinedZap
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.showAmountAxis
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications.showAmountAxis
|
||||||
import com.vitorpamplona.amethyst.ui.stringRes
|
import com.vitorpamplona.amethyst.ui.stringRes
|
||||||
@@ -83,12 +84,14 @@ import com.vitorpamplona.quartz.events.ChatroomKeyable
|
|||||||
import com.vitorpamplona.quartz.events.DraftEvent
|
import com.vitorpamplona.quartz.events.DraftEvent
|
||||||
import com.vitorpamplona.quartz.events.Event
|
import com.vitorpamplona.quartz.events.Event
|
||||||
import com.vitorpamplona.quartz.events.EventInterface
|
import com.vitorpamplona.quartz.events.EventInterface
|
||||||
|
import com.vitorpamplona.quartz.events.GenericRepostEvent
|
||||||
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
import com.vitorpamplona.quartz.events.GiftWrapEvent
|
||||||
import com.vitorpamplona.quartz.events.LnZapEvent
|
import com.vitorpamplona.quartz.events.LnZapEvent
|
||||||
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
import com.vitorpamplona.quartz.events.LnZapRequestEvent
|
||||||
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
import com.vitorpamplona.quartz.events.NIP90ContentDiscoveryResponseEvent
|
||||||
import com.vitorpamplona.quartz.events.Participant
|
import com.vitorpamplona.quartz.events.Participant
|
||||||
import com.vitorpamplona.quartz.events.ReportEvent
|
import com.vitorpamplona.quartz.events.ReportEvent
|
||||||
|
import com.vitorpamplona.quartz.events.RepostEvent
|
||||||
import com.vitorpamplona.quartz.events.Response
|
import com.vitorpamplona.quartz.events.Response
|
||||||
import com.vitorpamplona.quartz.events.SealedGossipEvent
|
import com.vitorpamplona.quartz.events.SealedGossipEvent
|
||||||
import com.vitorpamplona.quartz.events.SearchRelayListEvent
|
import com.vitorpamplona.quartz.events.SearchRelayListEvent
|
||||||
@@ -102,6 +105,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||||||
import kotlinx.collections.immutable.toImmutableSet
|
import kotlinx.collections.immutable.toImmutableSet
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.channels.BufferOverflow
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
@@ -110,7 +114,9 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.combineTransform
|
import kotlinx.coroutines.flow.combineTransform
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
@@ -120,7 +126,6 @@ import kotlinx.coroutines.suspendCancellableCoroutine
|
|||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.time.measureTimedValue
|
|
||||||
|
|
||||||
@Immutable open class ToastMsg
|
@Immutable open class ToastMsg
|
||||||
|
|
||||||
@@ -176,6 +181,82 @@ class AccountViewModel(
|
|||||||
|
|
||||||
val feedStates = AccountFeedContentStates(this)
|
val feedStates = AccountFeedContentStates(this)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
val notificationHasNewItems =
|
||||||
|
combineTransform(
|
||||||
|
account.loadLastReadFlow("Notification"),
|
||||||
|
feedStates.notifications.feedContent
|
||||||
|
.flatMapLatest {
|
||||||
|
if (it is CardFeedState.Loaded) {
|
||||||
|
it.feed
|
||||||
|
} else {
|
||||||
|
MutableStateFlow(null)
|
||||||
|
}
|
||||||
|
}.map { it?.list?.firstOrNull()?.createdAt() },
|
||||||
|
) { lastRead, newestItemCreatedAt ->
|
||||||
|
emit(newestItemCreatedAt != null && newestItemCreatedAt > lastRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
val notificationHasNewItemsFlow = notificationHasNewItems.flowOn(Dispatchers.Default).stateIn(viewModelScope, SharingStarted.Eagerly, false)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
val messagesHasNewItems =
|
||||||
|
feedStates.dmKnown.feedContent
|
||||||
|
.flatMapLatest {
|
||||||
|
if (it is FeedState.Loaded) {
|
||||||
|
it.feed
|
||||||
|
} else {
|
||||||
|
MutableStateFlow(null)
|
||||||
|
}
|
||||||
|
}.flatMapLatest {
|
||||||
|
val flows =
|
||||||
|
it?.list?.mapNotNull { chat ->
|
||||||
|
(chat.event as? ChatroomKeyable)?.let { event ->
|
||||||
|
val room = event.chatroomKey(account.signer.pubKey)
|
||||||
|
account.settings.getLastReadFlow("Room/${room.hashCode()}").map {
|
||||||
|
(chat.event?.createdAt() ?: 0) > it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flows != null) {
|
||||||
|
combine(flows) {
|
||||||
|
it.any { it }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MutableStateFlow(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val messagesHasNewItemsFlow = messagesHasNewItems.flowOn(Dispatchers.Default).stateIn(viewModelScope, SharingStarted.Eagerly, false)
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
val homeHasNewItems =
|
||||||
|
combineTransform(
|
||||||
|
account.loadLastReadFlow("HomeFollows"),
|
||||||
|
feedStates.homeNewThreads.feedContent
|
||||||
|
.flatMapLatest {
|
||||||
|
if (it is FeedState.Loaded) {
|
||||||
|
it.feed
|
||||||
|
} else {
|
||||||
|
MutableStateFlow(null)
|
||||||
|
}
|
||||||
|
}.map {
|
||||||
|
it?.list?.firstOrNull { it.event != null && it.event !is GenericRepostEvent && it.event !is RepostEvent }?.createdAt()
|
||||||
|
},
|
||||||
|
) { lastRead, newestItemCreatedAt ->
|
||||||
|
emit(newestItemCreatedAt != null && newestItemCreatedAt > lastRead)
|
||||||
|
}
|
||||||
|
|
||||||
|
val homeHasNewItemsFlow = homeHasNewItems.flowOn(Dispatchers.Default).stateIn(viewModelScope, SharingStarted.Eagerly, false)
|
||||||
|
|
||||||
|
val hasNewItems =
|
||||||
|
mapOf(
|
||||||
|
Route.Home to homeHasNewItemsFlow,
|
||||||
|
Route.Message to messagesHasNewItemsFlow,
|
||||||
|
Route.Notification to notificationHasNewItemsFlow,
|
||||||
|
)
|
||||||
|
|
||||||
fun clearToasts() {
|
fun clearToasts() {
|
||||||
viewModelScope.launch { toasts.emit(null) }
|
viewModelScope.launch { toasts.emit(null) }
|
||||||
}
|
}
|
||||||
@@ -1082,10 +1163,6 @@ class AccountViewModel(
|
|||||||
viewModelScope.launch(Dispatchers.IO) { onDone(OnlineChecker.isOnline(media)) }
|
viewModelScope.launch(Dispatchers.IO) { onDone(OnlineChecker.isOnline(media)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun refreshMarkAsReadObservers() {
|
|
||||||
updateNotificationDots()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadAndMarkAsRead(
|
fun loadAndMarkAsRead(
|
||||||
routeForLastRead: String,
|
routeForLastRead: String,
|
||||||
createdAt: Long?,
|
createdAt: Long?,
|
||||||
@@ -1098,9 +1175,7 @@ class AccountViewModel(
|
|||||||
|
|
||||||
if (onIsNew) {
|
if (onIsNew) {
|
||||||
viewModelScope.launch(Dispatchers.Default) {
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
if (account.markAsRead(routeForLastRead, createdAt)) {
|
account.markAsRead(routeForLastRead, createdAt)
|
||||||
refreshMarkAsReadObservers()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1112,8 +1187,6 @@ class AccountViewModel(
|
|||||||
onDone: () -> Unit,
|
onDone: () -> Unit,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
var atLeastOne = false
|
|
||||||
|
|
||||||
for (note in notes) {
|
for (note in notes) {
|
||||||
note.event?.let { noteEvent ->
|
note.event?.let { noteEvent ->
|
||||||
val channelHex = note.channelHex()
|
val channelHex = note.channelHex()
|
||||||
@@ -1128,16 +1201,10 @@ class AccountViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
route?.let {
|
route?.let {
|
||||||
if (account.markAsRead(route, noteEvent.createdAt())) {
|
account.markAsRead(route, noteEvent.createdAt())
|
||||||
atLeastOne = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (atLeastOne) {
|
|
||||||
refreshMarkAsReadObservers()
|
|
||||||
}
|
|
||||||
|
|
||||||
onDone()
|
onDone()
|
||||||
}
|
}
|
||||||
@@ -1178,21 +1245,8 @@ class AccountViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var collectorJob: Job? = null
|
private var collectorJob: Job? = null
|
||||||
val notificationDots = HasNotificationDot(bottomNavigationItems)
|
|
||||||
private val bundlerInsert = BundledInsert<Set<Note>>(3000, Dispatchers.Default)
|
private val bundlerInsert = BundledInsert<Set<Note>>(3000, Dispatchers.Default)
|
||||||
|
|
||||||
fun invalidateInsertData(newItems: Set<Note>) {
|
|
||||||
bundlerInsert.invalidateList(newItems) { updateNotificationDots(it.flatten().toSet()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateNotificationDots(newNotes: Set<Note> = emptySet()) {
|
|
||||||
val (value, elapsed) = measureTimedValue { notificationDots.update(newNotes, account) }
|
|
||||||
Log.d(
|
|
||||||
"Rendering Metrics",
|
|
||||||
"Notification Dots Calculation in $elapsed for ${newNotes.size} new notes",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Log.d("Init", "AccountViewModel")
|
Log.d("Init", "AccountViewModel")
|
||||||
collectorJob =
|
collectorJob =
|
||||||
@@ -1202,10 +1256,9 @@ class AccountViewModel(
|
|||||||
LocalCache.live.newEventBundles.collect { newNotes ->
|
LocalCache.live.newEventBundles.collect { newNotes ->
|
||||||
Log.d(
|
Log.d(
|
||||||
"Rendering Metrics",
|
"Rendering Metrics",
|
||||||
"Notification Dots Calculation refresh ${this@AccountViewModel} for ${account.userProfile().toBestDisplayName()}",
|
"Update feeds ${this@AccountViewModel} for ${account.userProfile().toBestDisplayName()}",
|
||||||
)
|
)
|
||||||
feedStates.updateFeedsWith(newNotes)
|
feedStates.updateFeedsWith(newNotes)
|
||||||
invalidateInsertData(newNotes)
|
|
||||||
upgradeAttestations()
|
upgradeAttestations()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1573,33 +1626,6 @@ class AccountViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class HasNotificationDot(
|
|
||||||
bottomNavigationItems: ImmutableList<Route>,
|
|
||||||
) {
|
|
||||||
val hasNewItems = bottomNavigationItems.associateWith { MutableStateFlow(false) }
|
|
||||||
|
|
||||||
fun update(
|
|
||||||
newNotes: Set<Note>,
|
|
||||||
account: Account,
|
|
||||||
) {
|
|
||||||
checkNotInMainThread()
|
|
||||||
|
|
||||||
hasNewItems.forEach {
|
|
||||||
val (value, elapsed) =
|
|
||||||
measureTimedValue {
|
|
||||||
val newResult = it.key.hasNewItems(account, newNotes)
|
|
||||||
if (newResult != it.value.value) {
|
|
||||||
it.value.value = newResult
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Log.d(
|
|
||||||
"Rendering Metrics",
|
|
||||||
"Notification Dots Calculation for ${it.key.route} in $elapsed for ${newNotes.size} new notes",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Immutable data class LoadedBechLink(
|
@Immutable data class LoadedBechLink(
|
||||||
val baseNote: Note?,
|
val baseNote: Note?,
|
||||||
val nip19: Nip19Bech32.ParseReturn,
|
val nip19: Nip19Bech32.ParseReturn,
|
||||||
|
Reference in New Issue
Block a user