Moves the last seen time saved per route to an account property in order to speed up loading and facilitate multithreading in the app.
This commit is contained in:
@@ -64,6 +64,7 @@ private object PrefKeys {
|
|||||||
const val SHOW_SENSITIVE_CONTENT = "show_sensitive_content"
|
const val SHOW_SENSITIVE_CONTENT = "show_sensitive_content"
|
||||||
const val WARN_ABOUT_REPORTS = "warn_about_reports"
|
const val WARN_ABOUT_REPORTS = "warn_about_reports"
|
||||||
const val FILTER_SPAM_FROM_STRANGERS = "filter_spam_from_strangers"
|
const val FILTER_SPAM_FROM_STRANGERS = "filter_spam_from_strangers"
|
||||||
|
const val LAST_READ_PER_ROUTE = "last_read_route_per_route"
|
||||||
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
|
val LAST_READ: (String) -> String = { route -> "last_read_route_$route" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,6 +224,7 @@ object LocalPreferences {
|
|||||||
putInt(PrefKeys.PROXY_PORT, account.proxyPort)
|
putInt(PrefKeys.PROXY_PORT, account.proxyPort)
|
||||||
putBoolean(PrefKeys.WARN_ABOUT_REPORTS, account.warnAboutPostsWithReports)
|
putBoolean(PrefKeys.WARN_ABOUT_REPORTS, account.warnAboutPostsWithReports)
|
||||||
putBoolean(PrefKeys.FILTER_SPAM_FROM_STRANGERS, account.filterSpamFromStrangers)
|
putBoolean(PrefKeys.FILTER_SPAM_FROM_STRANGERS, account.filterSpamFromStrangers)
|
||||||
|
putString(PrefKeys.LAST_READ_PER_ROUTE, gson.toJson(account.lastReadPerRoute))
|
||||||
|
|
||||||
if (account.showSensitiveContent == null) {
|
if (account.showSensitiveContent == null) {
|
||||||
remove(PrefKeys.SHOW_SENSITIVE_CONTENT)
|
remove(PrefKeys.SHOW_SENSITIVE_CONTENT)
|
||||||
@@ -320,6 +322,18 @@ object LocalPreferences {
|
|||||||
val filterSpam = getBoolean(PrefKeys.FILTER_SPAM_FROM_STRANGERS, true)
|
val filterSpam = getBoolean(PrefKeys.FILTER_SPAM_FROM_STRANGERS, true)
|
||||||
val warnAboutReports = getBoolean(PrefKeys.WARN_ABOUT_REPORTS, true)
|
val warnAboutReports = getBoolean(PrefKeys.WARN_ABOUT_REPORTS, true)
|
||||||
|
|
||||||
|
val lastReadPerRoute = try {
|
||||||
|
getString(PrefKeys.LAST_READ_PER_ROUTE, null)?.let {
|
||||||
|
gson.fromJson(
|
||||||
|
it,
|
||||||
|
object : TypeToken<Map<String, Long>>() {}.type
|
||||||
|
) as Map<String, Long>
|
||||||
|
} ?: mapOf()
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
e.printStackTrace()
|
||||||
|
mapOf()
|
||||||
|
}
|
||||||
|
|
||||||
val a = Account(
|
val a = Account(
|
||||||
loggedIn = Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
loggedIn = Persona(privKey = privKey?.hexToByteArray(), pubKey = pubKey.hexToByteArray()),
|
||||||
followingChannels = followingChannels,
|
followingChannels = followingChannels,
|
||||||
@@ -343,25 +357,14 @@ object LocalPreferences {
|
|||||||
proxyPort = proxyPort,
|
proxyPort = proxyPort,
|
||||||
showSensitiveContent = showSensitiveContent,
|
showSensitiveContent = showSensitiveContent,
|
||||||
warnAboutPostsWithReports = warnAboutReports,
|
warnAboutPostsWithReports = warnAboutReports,
|
||||||
filterSpamFromStrangers = filterSpam
|
filterSpamFromStrangers = filterSpam,
|
||||||
|
lastReadPerRoute = lastReadPerRoute
|
||||||
)
|
)
|
||||||
|
|
||||||
return a
|
return a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveLastRead(route: String, timestampInSecs: Long) {
|
|
||||||
encryptedPreferences(currentAccount()).edit().apply {
|
|
||||||
putLong(PrefKeys.LAST_READ(route), timestampInSecs)
|
|
||||||
}.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadLastRead(route: String): Long {
|
|
||||||
encryptedPreferences(currentAccount()).run {
|
|
||||||
return getLong(PrefKeys.LAST_READ(route), 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun migrateSingleUserPrefs() {
|
fun migrateSingleUserPrefs() {
|
||||||
if (currentAccount() != null) return
|
if (currentAccount() != null) return
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
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.launch
|
|
||||||
|
|
||||||
object NotificationCache {
|
|
||||||
// TODO: This must be account-based
|
|
||||||
val lastReadByRoute = mutableMapOf<String, Long>()
|
|
||||||
|
|
||||||
fun markAsRead(route: String, timestampInSecs: Long) {
|
|
||||||
val lastTime = lastReadByRoute[route]
|
|
||||||
if (lastTime == null || timestampInSecs > lastTime) {
|
|
||||||
lastReadByRoute.put(route, timestampInSecs)
|
|
||||||
|
|
||||||
val scope = CoroutineScope(Job() + Dispatchers.IO)
|
|
||||||
scope.launch {
|
|
||||||
LocalPreferences.saveLastRead(route, timestampInSecs)
|
|
||||||
live.invalidateData()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun load(route: String): Long {
|
|
||||||
var lastTime = lastReadByRoute[route]
|
|
||||||
if (lastTime == null) {
|
|
||||||
lastTime = LocalPreferences.loadLastRead(route)
|
|
||||||
lastReadByRoute[route] = lastTime
|
|
||||||
}
|
|
||||||
return lastTime
|
|
||||||
}
|
|
||||||
|
|
||||||
// Observers line up here.
|
|
||||||
val live: NotificationLiveData = NotificationLiveData(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
class NotificationLiveData(val cache: NotificationCache) : LiveData<NotificationState>(NotificationState(cache)) {
|
|
||||||
// Refreshes observers in batches.
|
|
||||||
private val bundler = BundledUpdate(300, Dispatchers.IO)
|
|
||||||
|
|
||||||
fun invalidateData() {
|
|
||||||
bundler.invalidate() {
|
|
||||||
if (hasActiveObservers()) {
|
|
||||||
postValue(NotificationState(cache))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class NotificationState(val cache: NotificationCache)
|
|
||||||
@@ -70,13 +70,15 @@ class Account(
|
|||||||
var proxyPort: Int,
|
var proxyPort: Int,
|
||||||
var showSensitiveContent: Boolean? = null,
|
var showSensitiveContent: Boolean? = null,
|
||||||
var warnAboutPostsWithReports: Boolean = true,
|
var warnAboutPostsWithReports: Boolean = true,
|
||||||
var filterSpamFromStrangers: Boolean = true
|
var filterSpamFromStrangers: Boolean = true,
|
||||||
|
var lastReadPerRoute: Map<String, Long> = mapOf<String, Long>()
|
||||||
) {
|
) {
|
||||||
var transientHiddenUsers: Set<String> = setOf()
|
var transientHiddenUsers: Set<String> = setOf()
|
||||||
|
|
||||||
// Observers line up here.
|
// Observers line up here.
|
||||||
val live: AccountLiveData = AccountLiveData(this)
|
val live: AccountLiveData = AccountLiveData(this)
|
||||||
val liveLanguages: AccountLiveData = AccountLiveData(this)
|
val liveLanguages: AccountLiveData = AccountLiveData(this)
|
||||||
|
val liveLastRead: AccountLiveData = AccountLiveData(this)
|
||||||
val saveable: AccountLiveData = AccountLiveData(this)
|
val saveable: AccountLiveData = AccountLiveData(this)
|
||||||
|
|
||||||
var userProfileCache: User? = null
|
var userProfileCache: User? = null
|
||||||
@@ -1154,6 +1156,19 @@ class Account(
|
|||||||
live.invalidateData()
|
live.invalidateData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun markAsRead(route: String, timestampInSecs: Long) {
|
||||||
|
val lastTime = lastReadPerRoute[route]
|
||||||
|
if (lastTime == null || timestampInSecs > lastTime) {
|
||||||
|
lastReadPerRoute = lastReadPerRoute + Pair(route, timestampInSecs)
|
||||||
|
saveable.invalidateData()
|
||||||
|
liveLastRead.invalidateData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadLastRead(route: String): Long {
|
||||||
|
return lastReadPerRoute[route] ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
fun registerObservers() {
|
fun registerObservers() {
|
||||||
// Observes relays to restart connections
|
// Observes relays to restart connections
|
||||||
userProfile().live().relays.observeForever {
|
userProfile().live().relays.observeForever {
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import androidx.navigation.NavBackStackEntry
|
import androidx.navigation.NavBackStackEntry
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
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
|
||||||
@@ -165,12 +164,11 @@ fun WatchPossibleNotificationChanges(
|
|||||||
val accountState by accountViewModel.accountLiveData.observeAsState()
|
val accountState by accountViewModel.accountLiveData.observeAsState()
|
||||||
val account = remember(accountState) { accountState?.account } ?: return
|
val account = remember(accountState) { accountState?.account } ?: return
|
||||||
|
|
||||||
val notifState by NotificationCache.live.observeAsState()
|
val notifState by accountViewModel.accountLastReadLiveData.observeAsState()
|
||||||
val notif = remember(notifState) { notifState?.cache } ?: return
|
|
||||||
|
|
||||||
LaunchedEffect(key1 = notifState, key2 = accountState) {
|
LaunchedEffect(key1 = notifState, key2 = accountState) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
onChange(route.hasNewItems(account, notif, emptySet()))
|
onChange(route.hasNewItems(account, emptySet()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +176,7 @@ fun WatchPossibleNotificationChanges(
|
|||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
LocalCache.live.newEventBundles.collect {
|
LocalCache.live.newEventBundles.collect {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
onChange(route.hasNewItems(account, notif, it))
|
onChange(route.hasNewItems(account, it))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import androidx.navigation.NavHostController
|
|||||||
import androidx.navigation.NavType
|
import androidx.navigation.NavType
|
||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.navArgument
|
import androidx.navigation.navArgument
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
@@ -27,7 +26,7 @@ import kotlinx.collections.immutable.toImmutableList
|
|||||||
sealed class Route(
|
sealed class Route(
|
||||||
val route: String,
|
val route: String,
|
||||||
val icon: Int,
|
val icon: Int,
|
||||||
val hasNewItems: (Account, NotificationCache, Set<com.vitorpamplona.amethyst.model.Note>) -> Boolean = { _, _, _ -> false },
|
val hasNewItems: (Account, Set<com.vitorpamplona.amethyst.model.Note>) -> Boolean = { _, _ -> false },
|
||||||
val arguments: ImmutableList<NamedNavArgument> = persistentListOf()
|
val arguments: ImmutableList<NamedNavArgument> = persistentListOf()
|
||||||
) {
|
) {
|
||||||
val base: String
|
val base: String
|
||||||
@@ -40,7 +39,7 @@ sealed class Route(
|
|||||||
navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false },
|
navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false },
|
||||||
navArgument("nip47") { type = NavType.StringType; nullable = true; defaultValue = null }
|
navArgument("nip47") { type = NavType.StringType; nullable = true; defaultValue = null }
|
||||||
).toImmutableList(),
|
).toImmutableList(),
|
||||||
hasNewItems = { accountViewModel, cache, newNotes -> HomeLatestItem.hasNewItems(accountViewModel, cache, newNotes) }
|
hasNewItems = { accountViewModel, newNotes -> HomeLatestItem.hasNewItems(accountViewModel, newNotes) }
|
||||||
)
|
)
|
||||||
|
|
||||||
object Search : Route(
|
object Search : Route(
|
||||||
@@ -59,13 +58,13 @@ sealed class Route(
|
|||||||
route = "Notification?scrollToTop={scrollToTop}",
|
route = "Notification?scrollToTop={scrollToTop}",
|
||||||
icon = R.drawable.ic_notifications,
|
icon = R.drawable.ic_notifications,
|
||||||
arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }).toImmutableList(),
|
arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }).toImmutableList(),
|
||||||
hasNewItems = { accountViewModel, cache, newNotes -> NotificationLatestItem.hasNewItems(accountViewModel, cache, newNotes) }
|
hasNewItems = { accountViewModel, newNotes -> NotificationLatestItem.hasNewItems(accountViewModel, newNotes) }
|
||||||
)
|
)
|
||||||
|
|
||||||
object Message : Route(
|
object Message : Route(
|
||||||
route = "Message",
|
route = "Message",
|
||||||
icon = R.drawable.ic_dm,
|
icon = R.drawable.ic_dm,
|
||||||
hasNewItems = { accountViewModel, cache, newNotes -> MessagesLatestItem.hasNewItems(accountViewModel, cache, newNotes) }
|
hasNewItems = { accountViewModel, newNotes -> MessagesLatestItem.hasNewItems(accountViewModel, newNotes) }
|
||||||
)
|
)
|
||||||
|
|
||||||
object BlockedUsers : Route(
|
object BlockedUsers : Route(
|
||||||
@@ -127,29 +126,40 @@ fun currentRoute(navController: NavHostController): String? {
|
|||||||
open class LatestItem {
|
open class LatestItem {
|
||||||
var newestItemPerAccount: Map<String, Note?> = mapOf()
|
var newestItemPerAccount: Map<String, Note?> = mapOf()
|
||||||
|
|
||||||
|
fun getNewestItem(account: Account): Note? {
|
||||||
|
return 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? {
|
fun updateNewestItem(newNotes: Set<Note>, account: Account, filter: AdditiveFeedFilter<Note>): Note? {
|
||||||
val newestItem = newestItemPerAccount[account.userProfile().pubkeyHex]
|
val newestItem = newestItemPerAccount[account.userProfile().pubkeyHex]
|
||||||
|
|
||||||
if (newestItem == null) {
|
if (newestItem == null) {
|
||||||
newestItemPerAccount = newestItemPerAccount + Pair(
|
newestItemPerAccount = newestItemPerAccount + Pair(
|
||||||
account.userProfile().pubkeyHex,
|
account.userProfile().pubkeyHex,
|
||||||
filterMore(filter.feed()).firstOrNull { it.createdAt() != null }
|
filterMore(filter.feed(), account).firstOrNull { it.createdAt() != null }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
newestItemPerAccount = newestItemPerAccount + Pair(
|
newestItemPerAccount = newestItemPerAccount + Pair(
|
||||||
account.userProfile().pubkeyHex,
|
account.userProfile().pubkeyHex,
|
||||||
filter.sort(filterMore(filter.applyFilter(newNotes)) + newestItem).first()
|
filter.sort(filterMore(filter.applyFilter(newNotes), account) + newestItem).first()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return newestItemPerAccount[account.userProfile().pubkeyHex]
|
return newestItemPerAccount[account.userProfile().pubkeyHex]
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun filterMore(newItems: Set<Note>): Set<Note> {
|
open fun filterMore(newItems: Set<Note>, account: Account): Set<Note> {
|
||||||
return newItems
|
return newItems
|
||||||
}
|
}
|
||||||
|
|
||||||
open fun filterMore(newItems: List<Note>): List<Note> {
|
open fun filterMore(newItems: List<Note>, account: Account): List<Note> {
|
||||||
return newItems
|
return newItems
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,10 +167,9 @@ open class LatestItem {
|
|||||||
object HomeLatestItem : LatestItem() {
|
object HomeLatestItem : LatestItem() {
|
||||||
fun hasNewItems(
|
fun hasNewItems(
|
||||||
account: Account,
|
account: Account,
|
||||||
cache: NotificationCache,
|
|
||||||
newNotes: Set<Note>
|
newNotes: Set<Note>
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val lastTime = cache.load("HomeFollows")
|
val lastTime = account.loadLastRead("HomeFollows")
|
||||||
|
|
||||||
val newestItem = updateNewestItem(newNotes, account, HomeNewThreadFeedFilter(account))
|
val newestItem = updateNewestItem(newNotes, account, HomeNewThreadFeedFilter(account))
|
||||||
|
|
||||||
@@ -171,10 +180,9 @@ object HomeLatestItem : LatestItem() {
|
|||||||
object NotificationLatestItem : LatestItem() {
|
object NotificationLatestItem : LatestItem() {
|
||||||
fun hasNewItems(
|
fun hasNewItems(
|
||||||
account: Account,
|
account: Account,
|
||||||
cache: NotificationCache,
|
|
||||||
newNotes: Set<Note>
|
newNotes: Set<Note>
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val lastTime = cache.load("Notification")
|
val lastTime = account.loadLastRead("Notification")
|
||||||
|
|
||||||
val newestItem = updateNewestItem(newNotes, account, NotificationFeedFilter(account))
|
val newestItem = updateNewestItem(newNotes, account, NotificationFeedFilter(account))
|
||||||
|
|
||||||
@@ -185,24 +193,53 @@ object NotificationLatestItem : LatestItem() {
|
|||||||
object MessagesLatestItem : LatestItem() {
|
object MessagesLatestItem : LatestItem() {
|
||||||
fun hasNewItems(
|
fun hasNewItems(
|
||||||
account: Account,
|
account: Account,
|
||||||
cache: NotificationCache,
|
|
||||||
newNotes: Set<Note>
|
newNotes: Set<Note>
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
println("AAA Hey")
|
||||||
|
|
||||||
|
// Checks if the current newest item is still unread.
|
||||||
|
// If so, there is no need to check anything else
|
||||||
|
if (isNew(getNewestItem(account), account)) {
|
||||||
|
println("AAA Enter ${getNewestItem(account)?.author?.toBestDisplayName()}")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
clearNewestItem(account)
|
||||||
|
|
||||||
|
println("AAA Hey 2")
|
||||||
|
|
||||||
|
// gets the newest of the unread items
|
||||||
val newestItem = updateNewestItem(newNotes, account, ChatroomListKnownFeedFilter(account))
|
val newestItem = updateNewestItem(newNotes, account, ChatroomListKnownFeedFilter(account))
|
||||||
|
|
||||||
val roomUserHex = (newestItem?.event as? PrivateDmEvent)?.talkingWith(account.userProfile().pubkeyHex)
|
println("AAA ${newestItem?.author?.toBestDisplayName()} ${isNew(newestItem, account)}")
|
||||||
|
|
||||||
val lastTime = cache.load("Room/$roomUserHex")
|
return isNew(newestItem, account)
|
||||||
|
|
||||||
return (newestItem?.createdAt() ?: 0) > lastTime
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun filterMore(newItems: Set<Note>): Set<Note> {
|
fun isNew(it: Note?, account: Account): Boolean {
|
||||||
return newItems.filter { it.event is PrivateDmEvent }.toSet()
|
if (it == null) return false
|
||||||
|
|
||||||
|
val currentUser = account.userProfile().pubkeyHex
|
||||||
|
val room = (it.event as? PrivateDmEvent)?.talkingWith(currentUser)
|
||||||
|
return if (room != null) {
|
||||||
|
val lastRead = account.loadLastRead("Room/$room")
|
||||||
|
(it.createdAt() ?: 0) > lastRead
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun filterMore(newItems: List<Note>): List<Note> {
|
override fun filterMore(newItems: Set<Note>, account: Account): Set<Note> {
|
||||||
return newItems.filter { it.event is PrivateDmEvent }
|
return newItems.filter {
|
||||||
|
isNew(it, account)
|
||||||
|
}.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun filterMore(newItems: List<Note>, account: Account): List<Note> {
|
||||||
|
return newItems.filter {
|
||||||
|
isNew(it, account)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import androidx.compose.ui.platform.LocalContext
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.screen.BadgeCard
|
import com.vitorpamplona.amethyst.ui.screen.BadgeCard
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -62,9 +61,9 @@ fun BadgeCompose(likeSetCard: BadgeCard, isInnerNote: Boolean = false, routeForL
|
|||||||
|
|
||||||
LaunchedEffect(key1 = likeSetCard) {
|
LaunchedEffect(key1 = likeSetCard) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
val isNew = likeSetCard.createdAt() > NotificationCache.load(routeForLastRead)
|
val isNew = likeSetCard.createdAt() > accountViewModel.account.loadLastRead(routeForLastRead)
|
||||||
|
|
||||||
NotificationCache.markAsRead(routeForLastRead, likeSetCard.createdAt())
|
accountViewModel.account.markAsRead(routeForLastRead, likeSetCard.createdAt())
|
||||||
|
|
||||||
val newBackgroundColor = if (isNew) {
|
val newBackgroundColor = if (isNew) {
|
||||||
newItemColor.compositeOver(defaultBackgroundColor)
|
newItemColor.compositeOver(defaultBackgroundColor)
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
@@ -74,7 +73,7 @@ fun ChatroomCompose(
|
|||||||
BlankNote(Modifier)
|
BlankNote(Modifier)
|
||||||
} else if (channelHex != null) {
|
} else if (channelHex != null) {
|
||||||
LoadChannel(baseChannelHex = channelHex!!) { channel ->
|
LoadChannel(baseChannelHex = channelHex!!) { channel ->
|
||||||
ChannelRoomCompose(note, channel, nav)
|
ChannelRoomCompose(note, channel, accountViewModel, nav)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val userRoomHex = remember(noteState, accountViewModel) {
|
val userRoomHex = remember(noteState, accountViewModel) {
|
||||||
@@ -91,6 +90,7 @@ fun ChatroomCompose(
|
|||||||
private fun ChannelRoomCompose(
|
private fun ChannelRoomCompose(
|
||||||
note: Note,
|
note: Note,
|
||||||
channel: Channel,
|
channel: Channel,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit
|
nav: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val authorState by note.author!!.live().metadata.observeAsState()
|
val authorState by note.author!!.live().metadata.observeAsState()
|
||||||
@@ -128,7 +128,7 @@ private fun ChannelRoomCompose(
|
|||||||
|
|
||||||
var hasNewMessages by remember { mutableStateOf<Boolean>(false) }
|
var hasNewMessages by remember { mutableStateOf<Boolean>(false) }
|
||||||
|
|
||||||
WatchNotificationChanges(note, route) { newHasNewMessages ->
|
WatchNotificationChanges(note, route, accountViewModel) { newHasNewMessages ->
|
||||||
if (hasNewMessages != newHasNewMessages) {
|
if (hasNewMessages != newHasNewMessages) {
|
||||||
hasNewMessages = newHasNewMessages
|
hasNewMessages = newHasNewMessages
|
||||||
}
|
}
|
||||||
@@ -182,7 +182,7 @@ private fun UserRoomCompose(
|
|||||||
"Room/${user.pubkeyHex}"
|
"Room/${user.pubkeyHex}"
|
||||||
}
|
}
|
||||||
|
|
||||||
WatchNotificationChanges(note, route) { newHasNewMessages ->
|
WatchNotificationChanges(note, route, accountViewModel) { newHasNewMessages ->
|
||||||
if (hasNewMessages != newHasNewMessages) {
|
if (hasNewMessages != newHasNewMessages) {
|
||||||
hasNewMessages = newHasNewMessages
|
hasNewMessages = newHasNewMessages
|
||||||
}
|
}
|
||||||
@@ -208,14 +208,15 @@ private fun UserRoomCompose(
|
|||||||
private fun WatchNotificationChanges(
|
private fun WatchNotificationChanges(
|
||||||
note: Note,
|
note: Note,
|
||||||
route: String,
|
route: String,
|
||||||
|
accountViewModel: AccountViewModel,
|
||||||
onNewStatus: (Boolean) -> Unit
|
onNewStatus: (Boolean) -> Unit
|
||||||
) {
|
) {
|
||||||
val cacheState by NotificationCache.live.observeAsState()
|
val cacheState by accountViewModel.accountLastReadLiveData.observeAsState()
|
||||||
|
|
||||||
LaunchedEffect(key1 = note, cacheState) {
|
LaunchedEffect(key1 = note, cacheState) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
note.event?.createdAt()?.let {
|
note.event?.createdAt()?.let {
|
||||||
val lastTime = NotificationCache.load(route)
|
val lastTime = accountViewModel.account.loadLastRead(route)
|
||||||
onNewStatus(it > lastTime)
|
onNewStatus(it > lastTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.IntSize
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
import com.vitorpamplona.amethyst.service.model.ChannelCreateEvent
|
||||||
@@ -184,11 +183,11 @@ fun ChatroomMessageCompose(
|
|||||||
LaunchedEffect(key1 = routeForLastRead) {
|
LaunchedEffect(key1 = routeForLastRead) {
|
||||||
routeForLastRead?.let {
|
routeForLastRead?.let {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
val lastTime = NotificationCache.load(it)
|
val lastTime = accountViewModel.account.loadLastRead(it)
|
||||||
|
|
||||||
val createdAt = note.createdAt()
|
val createdAt = note.createdAt()
|
||||||
if (createdAt != null) {
|
if (createdAt != null) {
|
||||||
NotificationCache.markAsRead(it, createdAt)
|
accountViewModel.account.markAsRead(it, createdAt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.compositeOver
|
import androidx.compose.ui.graphics.compositeOver
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.screen.MessageSetCard
|
import com.vitorpamplona.amethyst.ui.screen.MessageSetCard
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -53,9 +52,9 @@ fun MessageSetCompose(messageSetCard: MessageSetCard, routeForLastRead: String,
|
|||||||
|
|
||||||
LaunchedEffect(key1 = messageSetCard) {
|
LaunchedEffect(key1 = messageSetCard) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
val isNew = messageSetCard.createdAt() > NotificationCache.load(routeForLastRead)
|
val isNew = messageSetCard.createdAt() > accountViewModel.account.loadLastRead(routeForLastRead)
|
||||||
|
|
||||||
NotificationCache.markAsRead(routeForLastRead, messageSetCard.createdAt())
|
accountViewModel.account.markAsRead(routeForLastRead, messageSetCard.createdAt())
|
||||||
|
|
||||||
val newBackgroundColor = if (isNew) {
|
val newBackgroundColor = if (isNew) {
|
||||||
newItemColor.compositeOver(defaultBackgroundColor)
|
newItemColor.compositeOver(defaultBackgroundColor)
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
@@ -79,9 +78,9 @@ fun MultiSetCompose(multiSetCard: MultiSetCard, routeForLastRead: String, accoun
|
|||||||
|
|
||||||
LaunchedEffect(key1 = multiSetCard) {
|
LaunchedEffect(key1 = multiSetCard) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val isNew = multiSetCard.maxCreatedAt > NotificationCache.load(routeForLastRead)
|
val isNew = multiSetCard.maxCreatedAt > accountViewModel.account.loadLastRead(routeForLastRead)
|
||||||
|
|
||||||
NotificationCache.markAsRead(routeForLastRead, multiSetCard.maxCreatedAt)
|
accountViewModel.account.markAsRead(routeForLastRead, multiSetCard.maxCreatedAt)
|
||||||
|
|
||||||
val newBackgroundColor = if (isNew) {
|
val newBackgroundColor = if (isNew) {
|
||||||
newItemColor.compositeOver(defaultBackgroundColor)
|
newItemColor.compositeOver(defaultBackgroundColor)
|
||||||
|
|||||||
@@ -82,7 +82,6 @@ import androidx.core.graphics.get
|
|||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.compose.AsyncImagePainter
|
import coil.compose.AsyncImagePainter
|
||||||
import coil.request.SuccessResult
|
import coil.request.SuccessResult
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Channel
|
import com.vitorpamplona.amethyst.model.Channel
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
@@ -424,11 +423,11 @@ private fun CheckNewAndRenderNote(
|
|||||||
LaunchedEffect(key1 = routeForLastRead, key2 = parentBackgroundColor?.value) {
|
LaunchedEffect(key1 = routeForLastRead, key2 = parentBackgroundColor?.value) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
routeForLastRead?.let {
|
routeForLastRead?.let {
|
||||||
val lastTime = NotificationCache.load(it)
|
val lastTime = accountViewModel.account.loadLastRead(it)
|
||||||
|
|
||||||
val createdAt = baseNote.createdAt()
|
val createdAt = baseNote.createdAt()
|
||||||
if (createdAt != null) {
|
if (createdAt != null) {
|
||||||
NotificationCache.markAsRead(it, createdAt)
|
accountViewModel.account.markAsRead(it, createdAt)
|
||||||
|
|
||||||
val isNew = createdAt > lastTime
|
val isNew = createdAt > lastTime
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.graphics.compositeOver
|
import androidx.compose.ui.graphics.compositeOver
|
||||||
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 com.vitorpamplona.amethyst.NotificationCache
|
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.ui.screen.ZapUserSetCard
|
import com.vitorpamplona.amethyst.ui.screen.ZapUserSetCard
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -43,9 +42,9 @@ fun ZapUserSetCompose(zapSetCard: ZapUserSetCard, isInnerNote: Boolean = false,
|
|||||||
|
|
||||||
LaunchedEffect(key1 = zapSetCard.createdAt()) {
|
LaunchedEffect(key1 = zapSetCard.createdAt()) {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val isNew = zapSetCard.createdAt > NotificationCache.load(routeForLastRead)
|
val isNew = zapSetCard.createdAt > accountViewModel.account.loadLastRead(routeForLastRead)
|
||||||
|
|
||||||
NotificationCache.markAsRead(routeForLastRead, zapSetCard.createdAt)
|
accountViewModel.account.markAsRead(routeForLastRead, zapSetCard.createdAt)
|
||||||
|
|
||||||
val newBackgroundColor = if (isNew) {
|
val newBackgroundColor = if (isNew) {
|
||||||
newItemColor.compositeOver(defaultBackgroundColor)
|
newItemColor.compositeOver(defaultBackgroundColor)
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.vitorpamplona.amethyst.NotificationCache
|
|
||||||
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
import com.vitorpamplona.amethyst.service.model.PrivateDmEvent
|
||||||
import com.vitorpamplona.amethyst.ui.note.ChatroomCompose
|
import com.vitorpamplona.amethyst.ui.note.ChatroomCompose
|
||||||
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
|
||||||
@@ -103,7 +102,7 @@ private fun FeedLoaded(
|
|||||||
"Room/$roomUser"
|
"Room/$roomUser"
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationCache.markAsRead(route, it.createdAt())
|
accountViewModel.account.markAsRead(route, it.createdAt())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
markAsRead.value = false
|
markAsRead.value = false
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import java.util.Locale
|
|||||||
class AccountViewModel(val account: Account) : ViewModel() {
|
class AccountViewModel(val account: Account) : ViewModel() {
|
||||||
val accountLiveData: LiveData<AccountState> = account.live.map { it }
|
val accountLiveData: LiveData<AccountState> = account.live.map { it }
|
||||||
val accountLanguagesLiveData: LiveData<AccountState> = account.liveLanguages.map { it }
|
val accountLanguagesLiveData: LiveData<AccountState> = account.liveLanguages.map { it }
|
||||||
|
val accountLastReadLiveData: LiveData<AccountState> = account.liveLastRead.map { it }
|
||||||
|
|
||||||
fun isWriteable(): Boolean {
|
fun isWriteable(): Boolean {
|
||||||
return account.isWriteable()
|
return account.isWriteable()
|
||||||
|
|||||||
Reference in New Issue
Block a user