Moves home and Notification bubbles to the adaptive feed.

This commit is contained in:
Vitor Pamplona
2023-04-20 14:09:26 -04:00
parent 0a15e079dc
commit 3e27567532
3 changed files with 114 additions and 68 deletions
@@ -10,7 +10,7 @@ object ChatroomListKnownFeedFilter : FeedFilter<Note>() {
override fun feed(): List<Note> { override fun feed(): List<Note> {
val me = account.userProfile() val me = account.userProfile()
val privateChatrooms = account.userProfile().privateChatrooms val privateChatrooms = me.privateChatrooms
val messagingWith = privateChatrooms.keys.filter { val messagingWith = privateChatrooms.keys.filter {
me.hasSentMessagesTo(it) && account.isAcceptable(it) me.hasSentMessagesTo(it) && account.isAcceptable(it)
} }
@@ -1,6 +1,7 @@
package com.vitorpamplona.amethyst.ui.navigation package com.vitorpamplona.amethyst.ui.navigation
import android.graphics.Rect import android.graphics.Rect
import android.util.Log
import android.view.ViewTreeObserver import android.view.ViewTreeObserver
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
@@ -42,6 +43,8 @@ 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 kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.time.ExperimentalTime
import kotlin.time.measureTimedValue
val bottomNavigationItems = listOf( val bottomNavigationItems = listOf(
Route.Home, Route.Home,
@@ -135,66 +138,75 @@ 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) {
Box(Modifier.size(if ("Home" == route.base) 25.dp else 23.dp)) { println("Notifiable Icon")
Icon(
painter = painterResource(id = route.icon),
contentDescription = null,
modifier = Modifier.size(if ("Home" == route.base) 24.dp else 20.dp),
tint = if (selected) MaterialTheme.colors.primary else Color.Unspecified
)
val accountState by accountViewModel.accountLiveData.observeAsState() val (value, elapsed) = measureTimedValue {
val account = accountState?.account ?: return Box(Modifier.size(if ("Home" == route.base) 25.dp else 23.dp)) {
Icon(
painter = painterResource(id = route.icon),
contentDescription = null,
modifier = Modifier.size(if ("Home" == route.base) 24.dp else 20.dp),
tint = if (selected) MaterialTheme.colors.primary else Color.Unspecified
)
// Notification println("Notifiable Icon")
val dbState = LocalCache.live.observeAsState()
val db = dbState.value ?: return
val notifState = NotificationCache.live.observeAsState() val accountState by accountViewModel.accountLiveData.observeAsState()
val notif = notifState.value ?: return val account = accountState?.account ?: return
var hasNewItems by remember { mutableStateOf<Boolean>(false) } // Notification
val dbState = LocalCache.live.observeAsState()
val db = dbState.value ?: return
LaunchedEffect(key1 = notif) { val notifState = NotificationCache.live.observeAsState()
withContext(Dispatchers.IO) { val notif = notifState.value ?: return
hasNewItems = route.hasNewItems(account, notif.cache)
var hasNewItems by remember { mutableStateOf<Boolean>(false) }
LaunchedEffect(key1 = notif) {
withContext(Dispatchers.IO) {
hasNewItems = route.hasNewItems(account, notif.cache, emptySet())
}
} }
}
LaunchedEffect(key1 = db) { LaunchedEffect(key1 = db) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
hasNewItems = route.hasNewItems(account, notif.cache) hasNewItems = route.hasNewItems(account, notif.cache, db)
}
} }
}
if (hasNewItems) { if (hasNewItems) {
Box(
Modifier
.width(10.dp)
.height(10.dp)
.align(Alignment.TopEnd)
) {
Box( Box(
modifier = Modifier Modifier
.width(10.dp) .width(10.dp)
.height(10.dp) .height(10.dp)
.clip(shape = CircleShape) .align(Alignment.TopEnd)
.background(MaterialTheme.colors.primary),
contentAlignment = Alignment.TopEnd
) { ) {
Text( Box(
"",
color = Color.White,
textAlign = TextAlign.Center,
fontSize = 12.sp,
modifier = Modifier modifier = Modifier
.wrapContentHeight() .width(10.dp)
.align(Alignment.TopEnd) .height(10.dp)
) .clip(shape = CircleShape)
.background(MaterialTheme.colors.primary),
contentAlignment = Alignment.TopEnd
) {
Text(
"",
color = Color.White,
textAlign = TextAlign.Center,
fontSize = 12.sp,
modifier = Modifier
.wrapContentHeight()
.align(Alignment.TopEnd)
)
}
} }
} }
} }
} }
Log.d("Notification time", "$elapsed")
} }
@@ -10,6 +10,7 @@ import androidx.navigation.navArgument
import com.vitorpamplona.amethyst.NotificationCache 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.ui.dal.ChatroomListKnownFeedFilter import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter
import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter
import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
@@ -17,7 +18,7 @@ import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter
sealed class Route( sealed class Route(
val route: String, val route: String,
val icon: Int, val icon: Int,
val hasNewItems: (Account, NotificationCache) -> Boolean = { _, _ -> false }, val hasNewItems: (Account, NotificationCache, Set<com.vitorpamplona.amethyst.model.Note>) -> Boolean = { _, _, _ -> false },
val arguments: List<NamedNavArgument> = emptyList() val arguments: List<NamedNavArgument> = emptyList()
) { ) {
val base: String val base: String
@@ -30,7 +31,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 }
), ),
hasNewItems = { accountViewModel, cache -> homeHasNewItems(accountViewModel, cache) } hasNewItems = { accountViewModel, cache, newNotes -> HomeLatestItem.hasNewItems(accountViewModel, cache, newNotes) }
) )
object Search : Route( object Search : Route(
@@ -43,13 +44,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 }), arguments = listOf(navArgument("scrollToTop") { type = NavType.BoolType; defaultValue = false }),
hasNewItems = { accountViewModel, cache -> notificationHasNewItems(accountViewModel, cache) } hasNewItems = { accountViewModel, cache, newNotes -> NotificationLatestItem.hasNewItems(accountViewModel, cache, newNotes) }
) )
object Message : Route( object Message : Route(
route = "Message", route = "Message",
icon = R.drawable.ic_dm, icon = R.drawable.ic_dm,
hasNewItems = { accountViewModel, cache -> messagesHasNewItems(accountViewModel, cache) } hasNewItems = { accountViewModel, cache, newNotes -> MessagesLatestItem.hasNewItems(accountViewModel, cache, newNotes) }
) )
object BlockedUsers : Route( object BlockedUsers : Route(
@@ -108,36 +109,69 @@ fun currentRoute(navController: NavHostController): String? {
return navBackStackEntry?.destination?.route return navBackStackEntry?.destination?.route
} }
private fun homeHasNewItems(account: Account, cache: NotificationCache): Boolean { object HomeLatestItem {
val lastTime = cache.load("HomeFollows") private var newestItem: Note? = null
HomeNewThreadFeedFilter.account = account fun hasNewItems(
account: Account,
cache: NotificationCache,
newNotes: Set<Note>
): Boolean {
val lastTime = cache.load("HomeFollows")
HomeNewThreadFeedFilter.account = account
return ( if (newestItem == null) {
HomeNewThreadFeedFilter.feed().firstOrNull { it.createdAt() != null }?.createdAt() newestItem = HomeNewThreadFeedFilter.feed().firstOrNull { it.createdAt() != null }
?: 0 } else {
) > lastTime newestItem =
HomeNewThreadFeedFilter.sort(
HomeNewThreadFeedFilter.applyFilter(newNotes + newestItem!!)
).first()
}
return (newestItem?.createdAt() ?: 0) > lastTime
}
} }
private fun notificationHasNewItems(account: Account, cache: NotificationCache): Boolean { object NotificationLatestItem {
val lastTime = cache.load("Notification") private var newestItem: Note? = null
NotificationFeedFilter.account = account fun hasNewItems(
account: Account,
cache: NotificationCache,
newNotes: Set<Note>
): Boolean {
val lastTime = cache.load("Notification")
NotificationFeedFilter.account = account
return ( if (newestItem == null) {
NotificationFeedFilter.feed().firstOrNull { it.createdAt() != null }?.createdAt() newestItem = NotificationFeedFilter.feed().firstOrNull { it.createdAt() != null }
?: 0 } else {
) > lastTime newestItem = HomeNewThreadFeedFilter.sort(
NotificationFeedFilter.applyFilter(newNotes) + newestItem!!
).first()
}
return (newestItem?.createdAt() ?: 0) > lastTime
}
} }
private fun messagesHasNewItems(account: Account, cache: NotificationCache): Boolean { object MessagesLatestItem {
ChatroomListKnownFeedFilter.account = account private var newestItem: Note? = null
val note = ChatroomListKnownFeedFilter.feed().firstOrNull { fun hasNewItems(
it.createdAt() != null && it.channel() == null && it.author != account.userProfile() account: Account,
} ?: return false cache: NotificationCache,
newNotes: Set<Note>
): Boolean {
ChatroomListKnownFeedFilter.account = account
val lastTime = cache.load("Room/${note.author?.pubkeyHex}") val note = ChatroomListKnownFeedFilter.loadTop().firstOrNull {
it.createdAt() != null && it.channel() == null && it.author != account.userProfile()
} ?: return false
return (note.createdAt() ?: 0) > lastTime val lastTime = cache.load("Room/${note.author?.pubkeyHex}")
return (note.createdAt() ?: 0) > lastTime
}
} }