Browse Source

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

* Implement the network status indicator.

* Add `networkmonitor` feature.
test/jme/compound-poc
Jorge Martin Espinosa 1 year ago committed by GitHub
parent
commit
d391275420
  1. 1
      changelog.d/141.feature
  2. 2
      features/messages/impl/build.gradle.kts
  3. 9
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt
  4. 1
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt
  5. 2
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt
  6. 20
      features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt
  7. 2
      features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt
  8. 31
      features/networkmonitor/api/build.gradle.kts
  9. 24
      features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt
  10. 22
      features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt
  11. 125
      features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt
  12. 38
      features/networkmonitor/impl/build.gradle.kts
  13. 19
      features/networkmonitor/impl/src/main/AndroidManifest.xml
  14. 93
      features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt
  15. 30
      features/networkmonitor/test/build.gradle.kts
  16. 30
      features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt
  17. 2
      features/roomlist/impl/build.gradle.kts
  18. 6
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt
  19. 1
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt
  20. 2
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt
  21. 19
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt
  22. 4
      features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt
  23. 8
      features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt
  24. 5
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt
  25. 2
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt
  26. 2
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt
  27. 7
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt
  28. 1
      samples/minimal/build.gradle.kts
  29. 12
      samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt
  30. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png
  31. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png
  32. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png
  33. BIN
      tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_3,NEXUS_5,1.0,en].png

1
changelog.d/141.feature

@ -0,0 +1 @@
Add a `NetworkMonitor` component to track the network connection status

2
features/messages/impl/build.gradle.kts

@ -40,6 +40,7 @@ dependencies {
implementation(projects.libraries.textcomposer) implementation(projects.libraries.textcomposer)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.libraries.dateformatter.api) implementation(projects.libraries.dateformatter.api)
implementation(projects.features.networkmonitor.api)
implementation(libs.coil.compose) implementation(libs.coil.compose)
implementation(libs.datetime) implementation(libs.datetime)
implementation(libs.accompanist.flowlayout) implementation(libs.accompanist.flowlayout)
@ -53,6 +54,7 @@ dependencies {
testImplementation(libs.test.turbine) testImplementation(libs.test.turbine)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.dateformatter.test) testImplementation(projects.libraries.dateformatter.test)
testImplementation(projects.features.networkmonitor.test)
androidTestImplementation(libs.test.junitext) androidTestImplementation(libs.test.junitext)
ksp(libs.showkase.processor) ksp(libs.showkase.processor)

9
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt

@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@ -38,6 +39,8 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.textcomposer.MessageComposerMode import io.element.android.libraries.textcomposer.MessageComposerMode
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -48,6 +51,7 @@ class MessagesPresenter @Inject constructor(
private val composerPresenter: MessageComposerPresenter, private val composerPresenter: MessageComposerPresenter,
private val timelinePresenter: TimelinePresenter, private val timelinePresenter: TimelinePresenter,
private val actionListPresenter: ActionListPresenter, private val actionListPresenter: ActionListPresenter,
private val networkMonitor: NetworkMonitor,
) : Presenter<MessagesState> { ) : Presenter<MessagesState> {
@Composable @Composable
@ -64,6 +68,10 @@ class MessagesPresenter @Inject constructor(
val roomAvatar: MutableState<AvatarData?> = remember { val roomAvatar: MutableState<AvatarData?> = remember {
mutableStateOf(null) mutableStateOf(null)
} }
val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = networkMonitor.currentConnectivityStatus)
println(networkConnectionStatus)
LaunchedEffect(syncUpdateFlow) { LaunchedEffect(syncUpdateFlow) {
roomAvatar.value = roomAvatar.value =
AvatarData( AvatarData(
@ -89,6 +97,7 @@ class MessagesPresenter @Inject constructor(
composerState = composerState, composerState = composerState,
timelineState = timelineState, timelineState = timelineState,
actionListState = actionListState, actionListState = actionListState,
hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }

1
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt

@ -31,5 +31,6 @@ data class MessagesState(
val composerState: MessageComposerState, val composerState: MessageComposerState,
val timelineState: TimelineState, val timelineState: TimelineState,
val actionListState: ActionListState, val actionListState: ActionListState,
val hasNetworkConnection: Boolean,
val eventSink: (MessagesEvents) -> Unit val eventSink: (MessagesEvents) -> Unit
) )

2
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt

@ -31,6 +31,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
override val values: Sequence<MessagesState> override val values: Sequence<MessagesState>
get() = sequenceOf( get() = sequenceOf(
aMessagesState(), aMessagesState(),
aMessagesState().copy(hasNetworkConnection = false),
) )
} }
@ -47,5 +48,6 @@ fun aMessagesState() = MessagesState(
timelineItems = aTimelineItemList(aTimelineItemContent()), timelineItems = aTimelineItemList(aTimelineItemContent()),
), ),
actionListState = anActionListState(), actionListState = anActionListState(),
hasNetworkConnection = true,
eventSink = {} eventSink = {}
) )

20
features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt

@ -70,7 +70,7 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.utils.LogCompositions 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 kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -112,12 +112,15 @@ fun MessagesView(
modifier = modifier, modifier = modifier,
contentWindowInsets = WindowInsets.statusBars, contentWindowInsets = WindowInsets.statusBars,
topBar = { topBar = {
MessagesViewTopBar( Column {
roomTitle = state.roomName, ConnectivityIndicatorView(isOnline = state.hasNetworkConnection)
roomAvatar = state.roomAvatar, MessagesViewTopBar(
onBackPressed = onBackPressed, roomTitle = state.roomName,
onRoomDetailsClicked = onRoomDetailsClicked, roomAvatar = state.roomAvatar,
) onBackPressed = onBackPressed,
onRoomDetailsClicked = onRoomDetailsClicked,
)
}
}, },
content = { padding -> content = { padding ->
MessagesViewContent( MessagesViewContent(
@ -208,7 +211,8 @@ fun MessagesViewTopBar(
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
} }
} },
windowInsets = WindowInsets(0.dp)
) )
} }

2
features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt

@ -30,6 +30,7 @@ import io.element.android.features.messages.impl.actionlist.ActionListPresenter
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.textcomposer.MessageComposerPresenter import io.element.android.features.messages.impl.textcomposer.MessageComposerPresenter
import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.TimelinePresenter
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_ID
@ -141,6 +142,7 @@ class MessagesPresenterTest {
composerPresenter = messageComposerPresenter, composerPresenter = messageComposerPresenter,
timelinePresenter = timelinePresenter, timelinePresenter = timelinePresenter,
actionListPresenter = actionListPresenter, actionListPresenter = actionListPresenter,
networkMonitor = FakeNetworkMonitor(),
) )
} }
} }

31
features/networkmonitor/api/build.gradle.kts

@ -0,0 +1,31 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-compose-library")
}
android {
namespace = "io.element.android.features.networkmonitor.api"
}
dependencies {
implementation(libs.coroutines.core)
implementation(projects.libraries.designsystem)
implementation(projects.libraries.uiStrings)
}

24
features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt

@ -0,0 +1,24 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.networkmonitor.api
import kotlinx.coroutines.flow.Flow
interface NetworkMonitor {
val connectivity: Flow<NetworkStatus>
val currentConnectivityStatus: NetworkStatus
}

22
features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt

@ -0,0 +1,22 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.networkmonitor.api
enum class NetworkStatus {
Online,
Offline
}

125
features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt

@ -0,0 +1,125 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.networkmonitor.api.ui
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.MutableTransitionState
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.WifiOff
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.ElementTextStyles
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.LocalColors
import io.element.android.libraries.ui.strings.R as StringR
@Composable
fun ConnectivityIndicatorView(
isOnline: Boolean,
modifier: Modifier = Modifier
) {
val isIndicatorVisible = remember { MutableTransitionState(!isOnline) }.apply { targetState = !isOnline }
val isStatusBarPaddingVisible = remember { MutableTransitionState(isOnline) }.apply { targetState = isOnline }
// Display the network indicator with an animation
AnimatedVisibility(
visibleState = isIndicatorVisible,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
Indicator(modifier)
}
// Show missing status bar padding when the indicator is not visible
AnimatedVisibility(
visibleState = isStatusBarPaddingVisible,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
StatusBarPaddingSpacer(modifier)
}
}
@Composable
private fun Indicator(modifier: Modifier = Modifier) {
Row(
modifier
.fillMaxWidth()
.background(LocalColors.current.gray400)
.statusBarsPadding()
.padding(vertical = 6.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.Bottom,
) {
val tint = MaterialTheme.colorScheme.primary
Image(
imageVector = Icons.Outlined.WifiOff,
contentDescription = null,
colorFilter = ColorFilter.tint(tint),
modifier = Modifier.size(16.dp),
)
Spacer(modifier = Modifier.width(8.dp))
Text(text = stringResource(StringR.string.common_offline), style = ElementTextStyles.Regular.bodyMD, color = tint)
}
}
@Composable
private fun StatusBarPaddingSpacer(modifier: Modifier = Modifier) {
Spacer(modifier = modifier.statusBarsPadding())
}
@Preview
@Composable
internal fun PreviewLightConnectivityIndicatorView() {
ElementPreviewLight {
ConnectivityIndicatorView(isOnline = false)
}
}
@Preview
@Composable
internal fun PreviewDarkConnectivityIndicatorView() {
ElementPreviewDark {
ConnectivityIndicatorView(isOnline = false)
}
}

38
features/networkmonitor/impl/build.gradle.kts

@ -0,0 +1,38 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-library")
alias(libs.plugins.anvil)
}
anvil {
generateDaggerFactories.set(true)
}
android {
namespace = "io.element.android.features.networkmonitor.impl"
}
dependencies {
implementation(libs.coroutines.core)
implementation(libs.dagger)
implementation(projects.libraries.core)
implementation(projects.libraries.di)
api(projects.features.networkmonitor.api)
}

19
features/networkmonitor/impl/src/main/AndroidManifest.xml

@ -0,0 +1,19 @@
<!--
~ Copyright (c) 2023 New Vector Ltd
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

93
features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt

@ -0,0 +1,93 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.networkmonitor.impl
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.di.SingleIn
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import timber.log.Timber
import javax.inject.Inject
@ContributesBinding(scope = AppScope::class)
@SingleIn(AppScope::class)
class NetworkMonitorImpl @Inject constructor(
@ApplicationContext context: Context
) : NetworkMonitor {
private val connectivityManager: ConnectivityManager = context.getSystemService(ConnectivityManager::class.java)
private val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
_connectivity.value = connectivityManager.currentConnectionStatus()
Timber.v("Connectivity status (available): ${connectivityManager.currentConnectionStatus()}")
}
override fun onLost(network: Network) {
_connectivity.value = connectivityManager.currentConnectionStatus()
Timber.v("Connectivity status (lost): ${connectivityManager.currentConnectionStatus()}")
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
_connectivity.value = connectivityManager.currentConnectionStatus()
Timber.v("Connectivity status (changed): ${connectivityManager.currentConnectionStatus()}")
}
}
private val _connectivity = MutableStateFlow(NetworkStatus.Online)
override val connectivity: Flow<NetworkStatus> = _connectivity
override val currentConnectivityStatus: NetworkStatus get() = _connectivity.value
init {
listenToConnectionChanges()
}
private fun listenToConnectionChanges() {
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, callback)
_connectivity.tryEmit(connectivityManager.currentConnectionStatus())
}
private fun ConnectivityManager.currentConnectionStatus(): NetworkStatus {
val hasInternet = activeNetwork?.let(::getNetworkCapabilities)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
?: false
return if (hasInternet) {
NetworkStatus.Online
} else {
NetworkStatus.Offline
}
}
}

30
features/networkmonitor/test/build.gradle.kts

@ -0,0 +1,30 @@
/*
* Copyright (c) 2022 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed
@Suppress("DSL_SCOPE_VIOLATION")
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.features.networkmonitor.test"
}
dependencies {
api(projects.features.networkmonitor.api)
api(libs.coroutines.core)
}

30
features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt

@ -0,0 +1,30 @@
/*
* Copyright (c) 2023 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.element.android.features.networkmonitor.test
import io.element.android.features.networkmonitor.api.NetworkMonitor
import io.element.android.features.networkmonitor.api.NetworkStatus
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeNetworkMonitor(initialStatus: NetworkStatus = NetworkStatus.Online) : NetworkMonitor {
override val currentConnectivityStatus: NetworkStatus
get() = _connectivityStatus.value
private val _connectivityStatus: MutableStateFlow<NetworkStatus> = MutableStateFlow(initialStatus)
override val connectivity: Flow<NetworkStatus> = _connectivityStatus
}

2
features/roomlist/impl/build.gradle.kts

@ -48,6 +48,7 @@ dependencies {
implementation(projects.libraries.testtags) implementation(projects.libraries.testtags)
implementation(projects.libraries.uiStrings) implementation(projects.libraries.uiStrings)
implementation(projects.libraries.dateformatter.api) implementation(projects.libraries.dateformatter.api)
implementation(projects.features.networkmonitor.api)
implementation(libs.accompanist.placeholder) implementation(libs.accompanist.placeholder)
api(projects.features.roomlist.api) api(projects.features.roomlist.api)
ksp(libs.showkase.processor) ksp(libs.showkase.processor)
@ -60,6 +61,7 @@ dependencies {
testImplementation(libs.test.robolectric) testImplementation(libs.test.robolectric)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(projects.libraries.dateformatter.test) testImplementation(projects.libraries.dateformatter.test)
testImplementation(projects.features.networkmonitor.test)
testImplementation(projects.libraries.permissions.noop) testImplementation(projects.libraries.permissions.noop)
androidTestImplementation(libs.test.junitext) androidTestImplementation(libs.test.junitext)

6
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt

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

1
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt

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

2
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt

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

19
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt

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

4
features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt

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

8
features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt

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

5
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt

@ -70,5 +70,10 @@ val Vermilion = Color(0xFFFF5B55)
val LinkColor = Color(0xFF0086E6) val LinkColor = Color(0xFF0086E6)
// Compound colors
val TextColorCriticalLight = Color(0xFFD51928) val TextColorCriticalLight = Color(0xFFD51928)
val TextColorCriticalDark = Color(0xfffd3e3c) val TextColorCriticalDark = Color(0xfffd3e3c)
val Gray_400_Light = Color(0xFFE1E6EC)
val Gray_400_Dark = Color(0xFF26282D)

2
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt

@ -26,6 +26,7 @@ import io.element.android.libraries.designsystem.Black_950
import io.element.android.libraries.designsystem.DarkGrey import io.element.android.libraries.designsystem.DarkGrey
import io.element.android.libraries.designsystem.Gray_300 import io.element.android.libraries.designsystem.Gray_300
import io.element.android.libraries.designsystem.Gray_400 import io.element.android.libraries.designsystem.Gray_400
import io.element.android.libraries.designsystem.Gray_400_Dark
import io.element.android.libraries.designsystem.Gray_450 import io.element.android.libraries.designsystem.Gray_450
import io.element.android.libraries.designsystem.SystemGrey5Dark import io.element.android.libraries.designsystem.SystemGrey5Dark
import io.element.android.libraries.designsystem.SystemGrey6Dark import io.element.android.libraries.designsystem.SystemGrey6Dark
@ -38,6 +39,7 @@ fun elementColorsDark() = ElementColors(
messageHighlightedBackground = Azure, messageHighlightedBackground = Azure,
quaternary = Gray_400, quaternary = Gray_400,
quinary = Gray_450, quinary = Gray_450,
gray400 = Gray_400_Dark,
textActionCritical = TextColorCriticalDark, textActionCritical = TextColorCriticalDark,
isLight = false, isLight = false,
) )

2
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt

@ -26,6 +26,7 @@ import io.element.android.libraries.designsystem.Gray_100
import io.element.android.libraries.designsystem.Gray_150 import io.element.android.libraries.designsystem.Gray_150
import io.element.android.libraries.designsystem.Gray_200 import io.element.android.libraries.designsystem.Gray_200
import io.element.android.libraries.designsystem.Gray_25 import io.element.android.libraries.designsystem.Gray_25
import io.element.android.libraries.designsystem.Gray_400_Light
import io.element.android.libraries.designsystem.Gray_50 import io.element.android.libraries.designsystem.Gray_50
import io.element.android.libraries.designsystem.SystemGrey5Light import io.element.android.libraries.designsystem.SystemGrey5Light
import io.element.android.libraries.designsystem.SystemGrey6Light import io.element.android.libraries.designsystem.SystemGrey6Light
@ -38,6 +39,7 @@ fun elementColorsLight() = ElementColors(
messageHighlightedBackground = Azure, messageHighlightedBackground = Azure,
quaternary = Gray_100, quaternary = Gray_100,
quinary = Gray_50, quinary = Gray_50,
gray400 = Gray_400_Light,
textActionCritical = TextColorCriticalLight, textActionCritical = TextColorCriticalLight,
isLight = true, isLight = true,
) )

7
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt

@ -29,6 +29,7 @@ class ElementColors(
messageHighlightedBackground: Color, messageHighlightedBackground: Color,
quaternary: Color, quaternary: Color,
quinary: Color, quinary: Color,
gray400: Color,
textActionCritical: Color, textActionCritical: Color,
isLight: Boolean isLight: Boolean
) { ) {
@ -45,6 +46,9 @@ class ElementColors(
var quinary by mutableStateOf(quinary) var quinary by mutableStateOf(quinary)
private set private set
var gray400 by mutableStateOf(gray400)
private set
var textActionCritical by mutableStateOf(textActionCritical) var textActionCritical by mutableStateOf(textActionCritical)
private set private set
@ -57,6 +61,7 @@ class ElementColors(
messageHighlightedBackground: Color = this.messageHighlightedBackground, messageHighlightedBackground: Color = this.messageHighlightedBackground,
quaternary: Color = this.quaternary, quaternary: Color = this.quaternary,
quinary: Color = this.quinary, quinary: Color = this.quinary,
gray400: Color = this.gray400,
textActionCritical: Color = this.textActionCritical, textActionCritical: Color = this.textActionCritical,
isLight: Boolean = this.isLight, isLight: Boolean = this.isLight,
) = ElementColors( ) = ElementColors(
@ -65,6 +70,7 @@ class ElementColors(
messageHighlightedBackground = messageHighlightedBackground, messageHighlightedBackground = messageHighlightedBackground,
quaternary = quaternary, quaternary = quaternary,
quinary = quinary, quinary = quinary,
gray400 = gray400,
textActionCritical = textActionCritical, textActionCritical = textActionCritical,
isLight = isLight, isLight = isLight,
) )
@ -75,6 +81,7 @@ class ElementColors(
messageHighlightedBackground = other.messageHighlightedBackground messageHighlightedBackground = other.messageHighlightedBackground
quaternary = other.quaternary quaternary = other.quaternary
quinary = other.quinary quinary = other.quinary
gray400 = other.gray400
textActionCritical = other.textActionCritical textActionCritical = other.textActionCritical
isLight = other.isLight isLight = other.isLight
} }

1
samples/minimal/build.gradle.kts

@ -56,6 +56,7 @@ dependencies {
implementation(projects.libraries.dateformatter.impl) implementation(projects.libraries.dateformatter.impl)
implementation(projects.features.roomlist.impl) implementation(projects.features.roomlist.impl)
implementation(projects.features.login.impl) implementation(projects.features.login.impl)
implementation(projects.features.networkmonitor.impl)
implementation(libs.coroutines.core) implementation(libs.coroutines.core)
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3") coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
} }

12
samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt

@ -20,6 +20,7 @@ import android.content.Context
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import io.element.android.features.networkmonitor.impl.NetworkMonitorImpl
import io.element.android.features.roomlist.impl.DefaultRoomLastMessageFormatter import io.element.android.features.roomlist.impl.DefaultRoomLastMessageFormatter
import io.element.android.features.roomlist.impl.RoomListPresenter import io.element.android.features.roomlist.impl.RoomListPresenter
import io.element.android.features.roomlist.impl.RoomListView import io.element.android.features.roomlist.impl.RoomListView
@ -45,11 +46,12 @@ class RoomListScreen(
private val dateFormatters = DateFormatters(locale, clock, timeZone) private val dateFormatters = DateFormatters(locale, clock, timeZone)
private val sessionVerificationService = matrixClient.sessionVerificationService() private val sessionVerificationService = matrixClient.sessionVerificationService()
private val presenter = RoomListPresenter( private val presenter = RoomListPresenter(
matrixClient, client = matrixClient,
DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters), lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters),
DefaultRoomLastMessageFormatter(context, matrixClient), roomLastMessageFormatter = DefaultRoomLastMessageFormatter(context, matrixClient),
sessionVerificationService, sessionVerificationService = sessionVerificationService,
SnackbarDispatcher(), networkMonitor = NetworkMonitorImpl(context),
snackbarDispatcher = SnackbarDispatcher(),
) )
@Composable @Composable

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.messages.impl_null_DefaultGroup_MessagesViewLightPreview_0_null_1,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomlist.impl_null_DefaultGroup_RoomListViewLightPreview_0_null_3,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.
Loading…
Cancel
Save