[Room List] Show offline indicator when the device is offline (#239)

* Implement the network status indicator.

* Add `networkmonitor` feature.
This commit is contained in:
Jorge Martin Espinosa 2023-04-17 17:01:16 +02:00 committed by GitHub
parent 200763e15a
commit d391275420
33 changed files with 511 additions and 21 deletions

View file

@ -26,6 +26,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.features.roomlist.impl.model.RoomListRoomSummaryPlaceholders
import io.element.android.libraries.architecture.Presenter
@ -42,6 +43,7 @@ import io.element.android.libraries.matrix.api.room.RoomSummary
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.ui.model.MatrixUser
import io.element.android.features.networkmonitor.api.NetworkStatus
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
@ -57,6 +59,7 @@ class RoomListPresenter @Inject constructor(
private val lastMessageTimestampFormatter: LastMessageTimestampFormatter,
private val roomLastMessageFormatter: RoomLastMessageFormatter,
private val sessionVerificationService: SessionVerificationService,
private val networkMonitor: NetworkMonitor,
private val snackbarDispatcher: SnackbarDispatcher,
) : Presenter<RoomListState> {
@ -71,6 +74,8 @@ class RoomListPresenter @Inject constructor(
.roomSummaries()
.collectAsState()
val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = networkMonitor.currentConnectivityStatus)
Timber.v("RoomSummaries size = ${roomSummaries.size}")
val filteredRoomSummaries: MutableState<ImmutableList<RoomListRoomSummary>> = remember {
@ -108,6 +113,7 @@ class RoomListPresenter @Inject constructor(
filter = filter,
displayVerificationPrompt = displayVerificationPrompt,
snackbarMessage = snackbarMessage,
hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online,
eventSink = ::handleEvents
)
}

View file

@ -28,6 +28,7 @@ data class RoomListState(
val roomList: ImmutableList<RoomListRoomSummary>,
val filter: String,
val displayVerificationPrompt: Boolean,
val hasNetworkConnection: Boolean,
val snackbarMessage: SnackbarMessage?,
val eventSink: (RoomListEvents) -> Unit
)

View file

@ -33,6 +33,7 @@ open class RoomListStateProvider : PreviewParameterProvider<RoomListState> {
aRoomListState(),
aRoomListState().copy(displayVerificationPrompt = true),
aRoomListState().copy(snackbarMessage = SnackbarMessage(StringR.string.common_verification_complete)),
aRoomListState().copy(hasNetworkConnection = false),
)
}
@ -40,6 +41,7 @@ internal fun aRoomListState() = RoomListState(
matrixUser = MatrixUser(id = UserId("@id"), username = "User#1", avatarData = AvatarData("@id", "U")),
roomList = aRoomListRoomSummaryList(),
filter = "filter",
hasNetworkConnection = true,
snackbarMessage = null,
displayVerificationPrompt = false,
eventSink = {}

View file

@ -68,6 +68,7 @@ import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.utils.LogCompositions
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView
import kotlinx.coroutines.launch
import io.element.android.libraries.designsystem.R as DrawableR
import io.element.android.libraries.ui.strings.R as StringR
@ -150,14 +151,16 @@ fun RoomListContent(
Scaffold(
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
RoomListTopBar(
matrixUser = state.matrixUser,
filter = state.filter,
onFilterChanged = { state.eventSink(RoomListEvents.UpdateFilter(it)) },
onOpenSettings = onOpenSettings,
scrollBehavior = scrollBehavior,
modifier = Modifier,
)
Column {
ConnectivityIndicatorView(isOnline = state.hasNetworkConnection)
RoomListTopBar(
matrixUser = state.matrixUser,
filter = state.filter,
onFilterChanged = { state.eventSink(RoomListEvents.UpdateFilter(it)) },
onOpenSettings = onOpenSettings,
scrollBehavior = scrollBehavior,
)
}
},
content = { padding ->
Column(

View file

@ -19,6 +19,7 @@
package io.element.android.features.roomlist.impl.components
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.ContentAlpha
import androidx.compose.material.icons.Icons
@ -46,6 +47,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.element.android.features.roomlist.impl.R
import io.element.android.libraries.designsystem.components.avatar.Avatar
@ -172,6 +174,7 @@ fun SearchRoomListTopBar(
)
}
},
windowInsets = WindowInsets(0.dp)
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
@ -231,6 +234,7 @@ private fun DefaultRoomListTopBar(
}
},
scrollBehavior = scrollBehavior,
windowInsets = WindowInsets(0.dp),
)
}

View file

@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionClock
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
import io.element.android.libraries.dateformatter.api.LastMessageTimestampFormatter
import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter
@ -49,6 +50,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
@ -76,6 +78,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
@ -97,6 +100,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
@ -122,6 +126,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
@ -152,6 +157,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
@ -187,6 +193,7 @@ class RoomListPresenterTests {
createDateFormatter(),
FakeRoomLastMessageFormatter(),
FakeSessionVerificationService(),
FakeNetworkMonitor(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {
@ -236,6 +243,7 @@ class RoomListPresenterTests {
givenIsReady(true)
givenVerifiedStatus(SessionVerifiedStatus.NotVerified)
},
FakeNetworkMonitor(),
SnackbarDispatcher(),
)
moleculeFlow(RecompositionClock.Immediate) {