From d391275420a32b3ad03f511b6ecdb53d221e2736 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Mon, 17 Apr 2023 17:01:16 +0200 Subject: [PATCH] [Room List] Show offline indicator when the device is offline (#239) * Implement the network status indicator. * Add `networkmonitor` feature. --- changelog.d/141.feature | 1 + features/messages/impl/build.gradle.kts | 2 + .../messages/impl/MessagesPresenter.kt | 9 ++ .../features/messages/impl/MessagesState.kt | 1 + .../messages/impl/MessagesStateProvider.kt | 2 + .../features/messages/impl/MessagesView.kt | 20 +-- .../messages/MessagesPresenterTest.kt | 2 + features/networkmonitor/api/build.gradle.kts | 31 +++++ .../networkmonitor/api/NetworkMonitor.kt | 24 ++++ .../networkmonitor/api/NetworkStatus.kt | 22 +++ .../api/ui/ConnectivityIndicatorView.kt | 125 ++++++++++++++++++ features/networkmonitor/impl/build.gradle.kts | 38 ++++++ .../impl/src/main/AndroidManifest.xml | 19 +++ .../networkmonitor/impl/NetworkMonitorImpl.kt | 93 +++++++++++++ features/networkmonitor/test/build.gradle.kts | 30 +++++ .../networkmonitor/test/FakeNetworkMonitor.kt | 30 +++++ features/roomlist/impl/build.gradle.kts | 2 + .../roomlist/impl/RoomListPresenter.kt | 6 + .../features/roomlist/impl/RoomListState.kt | 1 + .../roomlist/impl/RoomListStateProvider.kt | 2 + .../features/roomlist/impl/RoomListView.kt | 19 +-- .../impl/components/RoomListTopBar.kt | 4 + .../roomlist/impl/RoomListPresenterTests.kt | 8 ++ .../android/libraries/designsystem/Color.kt | 5 + .../designsystem/theme/ColorsDark.kt | 2 + .../designsystem/theme/ColorsLight.kt | 2 + .../designsystem/theme/ElementColors.kt | 7 + samples/minimal/build.gradle.kts | 1 + .../android/samples/minimal/RoomListScreen.kt | 12 +- ...ewDarkPreview_0_null_1,NEXUS_5,1.0,en].png | 3 + ...wLightPreview_0_null_1,NEXUS_5,1.0,en].png | 3 + ...ewDarkPreview_0_null_3,NEXUS_5,1.0,en].png | 3 + ...wLightPreview_0_null_3,NEXUS_5,1.0,en].png | 3 + 33 files changed, 511 insertions(+), 21 deletions(-) create mode 100644 changelog.d/141.feature create mode 100644 features/networkmonitor/api/build.gradle.kts create mode 100644 features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt create mode 100644 features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt create mode 100644 features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt create mode 100644 features/networkmonitor/impl/build.gradle.kts create mode 100644 features/networkmonitor/impl/src/main/AndroidManifest.xml create mode 100644 features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt create mode 100644 features/networkmonitor/test/build.gradle.kts create mode 100644 features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt create mode 100644 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 create mode 100644 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 create mode 100644 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 create mode 100644 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 diff --git a/changelog.d/141.feature b/changelog.d/141.feature new file mode 100644 index 0000000000..3cf75bd484 --- /dev/null +++ b/changelog.d/141.feature @@ -0,0 +1 @@ +Add a `NetworkMonitor` component to track the network connection status diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 332e68a4b4..9b43d41d4d 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation(projects.libraries.textcomposer) implementation(projects.libraries.uiStrings) implementation(projects.libraries.dateformatter.api) + implementation(projects.features.networkmonitor.api) implementation(libs.coil.compose) implementation(libs.datetime) implementation(libs.accompanist.flowlayout) @@ -53,6 +54,7 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.dateformatter.test) + testImplementation(projects.features.networkmonitor.test) androidTestImplementation(libs.test.junitext) ksp(libs.showkase.processor) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index abef5e22f5..94edf64d58 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/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.MutableState import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember 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.matrix.api.room.MatrixRoom 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.launch import timber.log.Timber @@ -48,6 +51,7 @@ class MessagesPresenter @Inject constructor( private val composerPresenter: MessageComposerPresenter, private val timelinePresenter: TimelinePresenter, private val actionListPresenter: ActionListPresenter, + private val networkMonitor: NetworkMonitor, ) : Presenter { @Composable @@ -64,6 +68,10 @@ class MessagesPresenter @Inject constructor( val roomAvatar: MutableState = remember { mutableStateOf(null) } + + val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = networkMonitor.currentConnectivityStatus) + println(networkConnectionStatus) + LaunchedEffect(syncUpdateFlow) { roomAvatar.value = AvatarData( @@ -89,6 +97,7 @@ class MessagesPresenter @Inject constructor( composerState = composerState, timelineState = timelineState, actionListState = actionListState, + hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online, eventSink = ::handleEvents ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index 33d6cb2b59..88b25dd2d6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/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 timelineState: TimelineState, val actionListState: ActionListState, + val hasNetworkConnection: Boolean, val eventSink: (MessagesEvents) -> Unit ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 3cd929d591..e1abba7ac4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -31,6 +31,7 @@ open class MessagesStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aMessagesState(), + aMessagesState().copy(hasNetworkConnection = false), ) } @@ -47,5 +48,6 @@ fun aMessagesState() = MessagesState( timelineItems = aTimelineItemList(aTimelineItemContent()), ), actionListState = anActionListState(), + hasNetworkConnection = true, eventSink = {} ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index f59b35319c..132d139174 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/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.TopAppBar 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 timber.log.Timber @@ -112,12 +112,15 @@ fun MessagesView( modifier = modifier, contentWindowInsets = WindowInsets.statusBars, topBar = { - MessagesViewTopBar( - roomTitle = state.roomName, - roomAvatar = state.roomAvatar, - onBackPressed = onBackPressed, - onRoomDetailsClicked = onRoomDetailsClicked, - ) + Column { + ConnectivityIndicatorView(isOnline = state.hasNetworkConnection) + MessagesViewTopBar( + roomTitle = state.roomName, + roomAvatar = state.roomAvatar, + onBackPressed = onBackPressed, + onRoomDetailsClicked = onRoomDetailsClicked, + ) + } }, content = { padding -> MessagesViewContent( @@ -208,7 +211,8 @@ fun MessagesViewTopBar( overflow = TextOverflow.Ellipsis ) } - } + }, + windowInsets = WindowInsets(0.dp) ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index 64e41caed4..a7bc112174 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/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.textcomposer.MessageComposerPresenter 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.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.A_ROOM_ID @@ -141,6 +142,7 @@ class MessagesPresenterTest { composerPresenter = messageComposerPresenter, timelinePresenter = timelinePresenter, actionListPresenter = actionListPresenter, + networkMonitor = FakeNetworkMonitor(), ) } } diff --git a/features/networkmonitor/api/build.gradle.kts b/features/networkmonitor/api/build.gradle.kts new file mode 100644 index 0000000000..a9e6ed732d --- /dev/null +++ b/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) +} diff --git a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt new file mode 100644 index 0000000000..e85a61512d --- /dev/null +++ b/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 + val currentConnectivityStatus: NetworkStatus +} diff --git a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt new file mode 100644 index 0000000000..4a2f012384 --- /dev/null +++ b/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 +} diff --git a/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt new file mode 100644 index 0000000000..6603f8178b --- /dev/null +++ b/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) + } +} diff --git a/features/networkmonitor/impl/build.gradle.kts b/features/networkmonitor/impl/build.gradle.kts new file mode 100644 index 0000000000..1eacdef664 --- /dev/null +++ b/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) +} diff --git a/features/networkmonitor/impl/src/main/AndroidManifest.xml b/features/networkmonitor/impl/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..c168cfccc7 --- /dev/null +++ b/features/networkmonitor/impl/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt new file mode 100644 index 0000000000..ba4d6c2775 --- /dev/null +++ b/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 = _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 + } + } +} diff --git a/features/networkmonitor/test/build.gradle.kts b/features/networkmonitor/test/build.gradle.kts new file mode 100644 index 0000000000..4feb837780 --- /dev/null +++ b/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) +} diff --git a/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt b/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt new file mode 100644 index 0000000000..d7ccab0b26 --- /dev/null +++ b/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 = MutableStateFlow(initialStatus) + override val connectivity: Flow = _connectivityStatus +} diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index e4d826224a..3f5da09c79 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) implementation(projects.libraries.dateformatter.api) + implementation(projects.features.networkmonitor.api) implementation(libs.accompanist.placeholder) api(projects.features.roomlist.api) ksp(libs.showkase.processor) @@ -60,6 +61,7 @@ dependencies { testImplementation(libs.test.robolectric) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.dateformatter.test) + testImplementation(projects.features.networkmonitor.test) testImplementation(projects.libraries.permissions.noop) androidTestImplementation(libs.test.junitext) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt index 6f77ddf55a..f46cb7f01a 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListPresenter.kt +++ b/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.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 { @@ -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> = remember { @@ -108,6 +113,7 @@ class RoomListPresenter @Inject constructor( filter = filter, displayVerificationPrompt = displayVerificationPrompt, snackbarMessage = snackbarMessage, + hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online, eventSink = ::handleEvents ) } diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt index a14ef74e94..9da1a8d887 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListState.kt @@ -28,6 +28,7 @@ data class RoomListState( val roomList: ImmutableList, val filter: String, val displayVerificationPrompt: Boolean, + val hasNetworkConnection: Boolean, val snackbarMessage: SnackbarMessage?, val eventSink: (RoomListEvents) -> Unit ) diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt index 07e2fcff1a..855045ed6d 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListStateProvider.kt @@ -33,6 +33,7 @@ open class RoomListStateProvider : PreviewParameterProvider { 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 = {} diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt index b4c7676daf..d39feeac36 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt +++ b/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.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( diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt index 11abbd6e58..f9bdefd95e 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt +++ b/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 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), ) } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 3f3e43e2e7..d6643a1e5d 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/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.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) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt index 69cfa79df1..60f355d054 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/Color.kt @@ -70,5 +70,10 @@ val Vermilion = Color(0xFFFF5B55) val LinkColor = Color(0xFF0086E6) +// Compound colors + val TextColorCriticalLight = Color(0xFFD51928) val TextColorCriticalDark = Color(0xfffd3e3c) + +val Gray_400_Light = Color(0xFFE1E6EC) +val Gray_400_Dark = Color(0xFF26282D) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt index 42e97a5090..e697ed782c 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsDark.kt +++ b/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.Gray_300 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.SystemGrey5Dark import io.element.android.libraries.designsystem.SystemGrey6Dark @@ -38,6 +39,7 @@ fun elementColorsDark() = ElementColors( messageHighlightedBackground = Azure, quaternary = Gray_400, quinary = Gray_450, + gray400 = Gray_400_Dark, textActionCritical = TextColorCriticalDark, isLight = false, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt index 77ced0b9f4..35fdcf29b1 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ColorsLight.kt +++ b/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_200 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.SystemGrey5Light import io.element.android.libraries.designsystem.SystemGrey6Light @@ -38,6 +39,7 @@ fun elementColorsLight() = ElementColors( messageHighlightedBackground = Azure, quaternary = Gray_100, quinary = Gray_50, + gray400 = Gray_400_Light, textActionCritical = TextColorCriticalLight, isLight = true, ) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt index c15f0382c0..b275d66dd5 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/ElementColors.kt @@ -29,6 +29,7 @@ class ElementColors( messageHighlightedBackground: Color, quaternary: Color, quinary: Color, + gray400: Color, textActionCritical: Color, isLight: Boolean ) { @@ -45,6 +46,9 @@ class ElementColors( var quinary by mutableStateOf(quinary) private set + var gray400 by mutableStateOf(gray400) + private set + var textActionCritical by mutableStateOf(textActionCritical) private set @@ -57,6 +61,7 @@ class ElementColors( messageHighlightedBackground: Color = this.messageHighlightedBackground, quaternary: Color = this.quaternary, quinary: Color = this.quinary, + gray400: Color = this.gray400, textActionCritical: Color = this.textActionCritical, isLight: Boolean = this.isLight, ) = ElementColors( @@ -65,6 +70,7 @@ class ElementColors( messageHighlightedBackground = messageHighlightedBackground, quaternary = quaternary, quinary = quinary, + gray400 = gray400, textActionCritical = textActionCritical, isLight = isLight, ) @@ -75,6 +81,7 @@ class ElementColors( messageHighlightedBackground = other.messageHighlightedBackground quaternary = other.quaternary quinary = other.quinary + gray400 = other.gray400 textActionCritical = other.textActionCritical isLight = other.isLight } diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts index 8a264a2d27..6e2acf7e8a 100644 --- a/samples/minimal/build.gradle.kts +++ b/samples/minimal/build.gradle.kts @@ -56,6 +56,7 @@ dependencies { implementation(projects.libraries.dateformatter.impl) implementation(projects.features.roomlist.impl) implementation(projects.features.login.impl) + implementation(projects.features.networkmonitor.impl) implementation(libs.coroutines.core) coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3") } diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index 5dce2feafe..9911e1f969 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/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.DisposableEffect 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.RoomListPresenter import io.element.android.features.roomlist.impl.RoomListView @@ -45,11 +46,12 @@ class RoomListScreen( private val dateFormatters = DateFormatters(locale, clock, timeZone) private val sessionVerificationService = matrixClient.sessionVerificationService() private val presenter = RoomListPresenter( - matrixClient, - DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters), - DefaultRoomLastMessageFormatter(context, matrixClient), - sessionVerificationService, - SnackbarDispatcher(), + client = matrixClient, + lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters), + roomLastMessageFormatter = DefaultRoomLastMessageFormatter(context, matrixClient), + sessionVerificationService = sessionVerificationService, + networkMonitor = NetworkMonitorImpl(context), + snackbarDispatcher = SnackbarDispatcher(), ) @Composable diff --git a/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 b/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 new file mode 100644 index 0000000000..a2726d8d7f --- /dev/null +++ b/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 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8b1012920df3d9e6754e43464723139ae3fd0c04a518f761afcd54e25105acd6 +size 41437 diff --git a/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 b/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 new file mode 100644 index 0000000000..f2d984f169 --- /dev/null +++ b/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 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:59c0a62bf960d150ee111e88a932e9f5c85a82336a787b10db9f3c157ab13a35 +size 40520 diff --git a/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 b/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 new file mode 100644 index 0000000000..ab965efff2 --- /dev/null +++ b/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 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cb089bb34d22666fda661f7a9e95eae8f39a39f6ec3d64d45f6a18cb681cf228 +size 39817 diff --git a/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 b/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 new file mode 100644 index 0000000000..a7f9f94c2d --- /dev/null +++ b/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 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:54d8ec83ac35f79bb5d6a3dce30ba38a7e328e914454c98c2fd7a3a7e4a3fb46 +size 39315