mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 08:15:02 +00:00
Add total unread notification count to home screen (#7891)
* Add total unread notification count to home screen This will be calculated on each sync heartbeat (for now, using the state updates for the room list service). * Simplify `counter` parameter logic * Read the total unread count when the home screen is first displayed --------- Co-authored-by: Benoit Marty <benoitm@element.io>
This commit is contained in:
committed by
GitHub
parent
50aa723f63
commit
9cc5cf9a01
+17
@@ -14,6 +14,7 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@@ -34,9 +35,14 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.FlowPreview
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.debounce
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@Inject
|
||||
class HomePresenter(
|
||||
@@ -53,10 +59,20 @@ class HomePresenter(
|
||||
) : Presenter<HomeState> {
|
||||
private val currentUserWithNeighborsBuilder = CurrentUserWithNeighborsBuilder()
|
||||
|
||||
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
|
||||
@Composable
|
||||
override fun present(): HomeState {
|
||||
val coroutineState = rememberCoroutineScope()
|
||||
val matrixUser by client.userProfile.collectAsState()
|
||||
|
||||
val totalUnreadCount by produceState(client.totalUnreadCount().getOrDefault(0).toInt()) {
|
||||
syncService.heartBeat
|
||||
// Delay the heartbeat so the SDK has time to update the unread count before we read it.
|
||||
.debounce(100.milliseconds)
|
||||
.mapLatest { client.totalUnreadCount().getOrDefault(0).toInt() }
|
||||
.collect { value = it }
|
||||
}
|
||||
|
||||
val currentUserAndNeighbors by remember {
|
||||
combine(
|
||||
client.userProfile,
|
||||
@@ -130,6 +146,7 @@ class HomePresenter(
|
||||
snackbarMessage = snackbarMessage,
|
||||
canReportBug = canReportBug,
|
||||
showMultiAccountAnnouncement = showMultiAccountAnnouncement,
|
||||
totalUnreadCount = totalUnreadCount,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ data class HomeState(
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
val canReportBug: Boolean,
|
||||
val showMultiAccountAnnouncement: Boolean,
|
||||
val totalUnreadCount: Int,
|
||||
val eventSink: (HomeEvent) -> Unit,
|
||||
) {
|
||||
val isBackHandlerEnabled = currentHomeNavigationBarItem != HomeNavigationBarItem.Chats || roomListState.spaceFiltersState is SpaceFiltersState.Selected
|
||||
|
||||
+2
@@ -64,6 +64,7 @@ internal fun aHomeState(
|
||||
homeSpacesState: HomeSpacesState = aHomeSpacesState(),
|
||||
canReportBug: Boolean = true,
|
||||
showMultiAccountAnnouncement: Boolean = false,
|
||||
totalUnreadCount: Int = 0,
|
||||
eventSink: (HomeEvent) -> Unit = {}
|
||||
) = HomeState(
|
||||
currentUserAndNeighbors = currentUserAndNeighbors.toImmutableList(),
|
||||
@@ -75,5 +76,6 @@ internal fun aHomeState(
|
||||
currentHomeNavigationBarItem = currentHomeNavigationBarItem,
|
||||
roomListState = roomListState,
|
||||
homeSpacesState = homeSpacesState,
|
||||
totalUnreadCount = totalUnreadCount,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
@@ -259,6 +259,7 @@ private fun HomeScaffold(
|
||||
}
|
||||
}
|
||||
},
|
||||
totalUnreadCount = state.totalUnreadCount,
|
||||
)
|
||||
},
|
||||
floatingActionButtonPosition = FabPosition.Center,
|
||||
@@ -341,6 +342,7 @@ private fun HomeFloatingActionButton(
|
||||
|
||||
@Composable
|
||||
private fun HomeBottomBar(
|
||||
totalUnreadCount: Int,
|
||||
currentHomeNavigationBarItem: HomeNavigationBarItem,
|
||||
onItemClick: (HomeNavigationBarItem) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
@@ -361,6 +363,7 @@ private fun HomeBottomBar(
|
||||
tooltipLabel = stringResource(item.labelRes),
|
||||
isSelected = isSelected,
|
||||
onClick = { onItemClick(item) },
|
||||
counter = totalUnreadCount.takeIf { item == HomeNavigationBarItem.Chats && it > 0 }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+44
@@ -40,9 +40,11 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class HomePresenterTest {
|
||||
@get:Rule
|
||||
@@ -117,6 +119,38 @@ class HomePresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - total unread count is updated on sync heartbeat`() = runTest {
|
||||
var unreadCount = 0L
|
||||
val syncService = FakeSyncService()
|
||||
val presenter = createHomePresenter(
|
||||
client = FakeMatrixClient(totalUnreadCountResult = { Result.success(unreadCount) }),
|
||||
syncService = syncService,
|
||||
)
|
||||
presenter.test {
|
||||
assertThat(awaitItem().totalUnreadCount).isEqualTo(0)
|
||||
unreadCount = 5
|
||||
syncService.emitHeartBeat()
|
||||
advanceTimeBy(200.milliseconds)
|
||||
assertThat(awaitItem().totalUnreadCount).isEqualTo(5)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - total unread count defaults to 0 when the SDK call fails`() = runTest {
|
||||
val syncService = FakeSyncService()
|
||||
val presenter = createHomePresenter(
|
||||
client = FakeMatrixClient(totalUnreadCountResult = { Result.failure(AN_EXCEPTION) }),
|
||||
syncService = syncService,
|
||||
)
|
||||
presenter.test {
|
||||
assertThat(awaitItem().totalUnreadCount).isEqualTo(0)
|
||||
syncService.emitHeartBeat()
|
||||
advanceTimeBy(200.milliseconds)
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - should start with no user and then load user with error`() = runTest {
|
||||
val matrixClient = FakeMatrixClient(
|
||||
@@ -245,6 +279,16 @@ class HomePresenterTest {
|
||||
.with(value(Announcement.MultiAccount))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - total unread count is loaded in the initial state`() = runTest {
|
||||
val presenter = createHomePresenter(
|
||||
client = FakeMatrixClient(totalUnreadCountResult = { Result.success(5) }),
|
||||
)
|
||||
presenter.test {
|
||||
assertThat(awaitItem().totalUnreadCount).isEqualTo(5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun createHomePresenter(
|
||||
|
||||
Reference in New Issue
Block a user