From 73d12e866595322685bad1a02c32152c948044fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Tue, 21 Mar 2023 11:16:39 +0100 Subject: [PATCH 1/5] Implement the network status indicator --- features/messages/impl/build.gradle.kts | 2 + .../messages/impl/MessagesPresenter.kt | 8 ++ .../features/messages/impl/MessagesState.kt | 1 + .../messages/impl/MessagesStateProvider.kt | 2 + .../features/messages/impl/MessagesView.kt | 20 ++-- .../messages/MessagesPresenterTest.kt | 2 + 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 | 20 ++-- .../impl/components/RoomListTopBar.kt | 5 + .../roomlist/impl/RoomListPresenterTests.kt | 9 ++ .../android/libraries/designsystem/Color.kt | 5 + .../designsystem/theme/ColorsDark.kt | 2 + .../designsystem/theme/ColorsLight.kt | 2 + .../designsystem/theme/ElementColors.kt | 7 ++ .../kotlin/extension/DependencyHandleScope.kt | 1 + samples/minimal/build.gradle.kts | 1 + .../android/samples/minimal/RoomListScreen.kt | 10 +- services/networkmonitor/api/build.gradle.kts | 30 +++++ .../networkmonitor/api/NetworkMonitor.kt | 23 ++++ .../networkmonitor/api/NetworkStatus.kt | 22 ++++ .../api/ui/ConnectivityIndicatorView.kt | 104 ++++++++++++++++++ services/networkmonitor/impl/build.gradle.kts | 38 +++++++ .../impl/src/main/AndroidManifest.xml | 19 ++++ .../networkmonitor/impl/NetworkMonitorImpl.kt | 100 +++++++++++++++++ services/networkmonitor/test/build.gradle.kts | 30 +++++ .../networkmonitor/test/FakeNetworkMonitor.kt | 24 ++++ ...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, 490 insertions(+), 20 deletions(-) create mode 100644 services/networkmonitor/api/build.gradle.kts create mode 100644 services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkMonitor.kt create mode 100644 services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkStatus.kt create mode 100644 services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/ui/ConnectivityIndicatorView.kt create mode 100644 services/networkmonitor/impl/build.gradle.kts create mode 100644 services/networkmonitor/impl/src/main/AndroidManifest.xml create mode 100644 services/networkmonitor/impl/src/main/kotlin/io/element/android/services/networkmonitor/impl/NetworkMonitorImpl.kt create mode 100644 services/networkmonitor/test/build.gradle.kts create mode 100644 services/networkmonitor/test/src/main/kotlin/io/element/android/services/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/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 64b7139fb85..592f0d8083f 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -42,6 +42,7 @@ dependencies { implementation(projects.libraries.textcomposer) implementation(projects.libraries.uiStrings) implementation(projects.libraries.dateformatter.api) + implementation(projects.services.networkmonitor.api) implementation(libs.coil.compose) implementation(libs.datetime) implementation(libs.accompanist.flowlayout) @@ -55,6 +56,7 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.dateformatter.test) + testImplementation(projects.services.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 abef5e22f50..f815d6a4e0b 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.services.networkmonitor.api.NetworkMonitor +import io.element.android.services.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,9 @@ class MessagesPresenter @Inject constructor( val roomAvatar: MutableState = remember { mutableStateOf(null) } + + val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = NetworkStatus.Online) + LaunchedEffect(syncUpdateFlow) { roomAvatar.value = AvatarData( @@ -89,6 +96,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 33d6cb2b59e..88b25dd2d6c 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 3cd929d5917..e1abba7ac41 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 f59b35319c3..6a2eef27c9e 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.services.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 64e41caed42..7a5fda1b2ca 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 @@ -35,6 +35,7 @@ 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.room.FakeMatrixRoom import io.element.android.libraries.textcomposer.MessageComposerMode +import io.element.android.services.networkmonitor.test.FakeNetworkMonitor import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -141,6 +142,7 @@ class MessagesPresenterTest { composerPresenter = messageComposerPresenter, timelinePresenter = timelinePresenter, actionListPresenter = actionListPresenter, + networkMonitor = FakeNetworkMonitor(), ) } } diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index e865527b802..6a78281ee6c 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -49,6 +49,7 @@ dependencies { implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) implementation(projects.libraries.dateformatter.api) + implementation(projects.services.networkmonitor.api) implementation(libs.accompanist.placeholder) api(projects.features.roomlist.api) ksp(libs.showkase.processor) @@ -61,6 +62,7 @@ dependencies { testImplementation(libs.test.robolectric) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.dateformatter.test) + testImplementation(projects.services.networkmonitor.test) 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 7d9a62f2d73..016850c76b9 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 @@ -41,6 +41,8 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.ui.model.MatrixUser +import io.element.android.services.networkmonitor.api.NetworkMonitor +import io.element.android.services.networkmonitor.api.NetworkStatus import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -56,6 +58,7 @@ class RoomListPresenter @Inject constructor( private val lastMessageTimestampFormatter: LastMessageTimestampFormatter, private val roomLastMessageFormatter: RoomLastMessageFormatter, private val sessionVerificationService: SessionVerificationService, + private val networkMonitor: NetworkMonitor, ) : Presenter { @Composable @@ -69,6 +72,8 @@ class RoomListPresenter @Inject constructor( .roomSummaries() .collectAsState() + val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = NetworkStatus.Online) + Timber.v("RoomSummaries size = ${roomSummaries.size}") val filteredRoomSummaries: MutableState> = remember { @@ -112,6 +117,7 @@ class RoomListPresenter @Inject constructor( filter = filter, presentVerificationSuccessfulMessage = presentVerificationSuccessfulMessage.value, displayVerificationPrompt = displayVerificationPrompt, + 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 122c5e55068..955d58b6a47 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,5 +28,6 @@ data class RoomListState( val filter: String, val presentVerificationSuccessfulMessage: Boolean, val displayVerificationPrompt: Boolean, + val hasNetworkConnection: Boolean, 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 68db6316752..ce97d47f378 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 @@ -31,6 +31,7 @@ open class RoomListStateProvider : PreviewParameterProvider { aRoomListState(), aRoomListState().copy(displayVerificationPrompt = true), aRoomListState().copy(presentVerificationSuccessfulMessage = true), + aRoomListState().copy(hasNetworkConnection = false), ) } @@ -40,6 +41,7 @@ internal fun aRoomListState() = RoomListState( filter = "filter", eventSink = {}, presentVerificationSuccessfulMessage = false, + hasNetworkConnection = true, displayVerificationPrompt = false, ) 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 8d31b17a141..c67a7f55974 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 @@ -67,6 +67,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.services.networkmonitor.api.ui.ConnectivityIndicatorView import io.element.android.libraries.designsystem.R as DrawableR import io.element.android.libraries.ui.strings.R as StringR @@ -144,14 +145,17 @@ 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, + useStatusBarInsets = state.hasNetworkConnection, + ) + } }, 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 11abbd6e589..198280a24ca 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 @@ -71,6 +73,7 @@ fun RoomListTopBar( onFilterChanged: (String) -> Unit, onOpenSettings: () -> Unit, scrollBehavior: TopAppBarScrollBehavior, + useStatusBarInsets: Boolean, modifier: Modifier = Modifier, ) { LogCompositions( @@ -172,6 +175,7 @@ fun SearchRoomListTopBar( ) } }, + windowInsets = WindowInsets(0.dp) ) LaunchedEffect(Unit) { focusRequester.requestFocus() @@ -231,6 +235,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 1e8c15bf6d1..86cc2e27cb2 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 @@ -37,6 +37,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService +import io.element.android.services.networkmonitor.test.FakeNetworkMonitor import kotlinx.coroutines.test.runTest import org.junit.Test @@ -49,6 +50,7 @@ class RoomListPresenterTests { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + FakeNetworkMonitor(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -75,6 +77,7 @@ class RoomListPresenterTests { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + FakeNetworkMonitor(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -95,6 +98,7 @@ class RoomListPresenterTests { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + FakeNetworkMonitor(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -119,6 +123,7 @@ class RoomListPresenterTests { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + FakeNetworkMonitor(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -148,6 +153,7 @@ class RoomListPresenterTests { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + FakeNetworkMonitor(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -182,6 +188,7 @@ class RoomListPresenterTests { createDateFormatter(), FakeRoomLastMessageFormatter(), FakeSessionVerificationService(), + FakeNetworkMonitor(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -230,6 +237,7 @@ class RoomListPresenterTests { givenIsReady(true) givenVerifiedStatus(SessionVerifiedStatus.NotVerified) }, + FakeNetworkMonitor(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() @@ -256,6 +264,7 @@ class RoomListPresenterTests { givenIsReady(true) givenVerificationFlowState(VerificationFlowState.Finished) }, + FakeNetworkMonitor(), ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() 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 69cfa79df12..60f355d0542 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 42e97a5090e..e697ed782cd 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 77ced0b9f45..35fdcf29b13 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 c15f0382c05..b275d66dd5d 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/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 314421ebc85..e157acaf73f 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -84,6 +84,7 @@ fun DependencyHandlerScope.allLibrariesImpl() { fun DependencyHandlerScope.allServicesImpl() { implementation(project(":services:analytics:noop")) implementation(project(":services:appnavstate:impl")) + implementation(project(":services:networkmonitor:impl")) implementation(project(":services:toolbox:impl")) } diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts index 47f5675a079..4815cfff85d 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.services.networkmonitor.impl) implementation(libs.coroutines.core) coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2") } 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 6c95dee2102..8c5b45da6ec 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 @@ -28,6 +28,7 @@ import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimesta import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.services.networkmonitor.impl.NetworkMonitorImpl import kotlinx.coroutines.launch import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone @@ -44,10 +45,11 @@ 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 + client = matrixClient, + lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter(dateTimeProvider, dateFormatters), + roomLastMessageFormatter = DefaultRoomLastMessageFormatter(context, matrixClient), + sessionVerificationService = sessionVerificationService, + networkMonitor = NetworkMonitorImpl(context), ) @Composable diff --git a/services/networkmonitor/api/build.gradle.kts b/services/networkmonitor/api/build.gradle.kts new file mode 100644 index 00000000000..2518a173e45 --- /dev/null +++ b/services/networkmonitor/api/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-compose-library") +} + +android { + namespace = "io.element.android.services.networkmonitor.api" +} + +dependencies { + implementation(libs.coroutines.core) + implementation(projects.libraries.designsystem) +} diff --git a/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkMonitor.kt b/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkMonitor.kt new file mode 100644 index 00000000000..cf707791ced --- /dev/null +++ b/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkMonitor.kt @@ -0,0 +1,23 @@ +/* + * 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.services.networkmonitor.api + +import kotlinx.coroutines.flow.Flow + +interface NetworkMonitor { + val connectivity: Flow +} diff --git a/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkStatus.kt b/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkStatus.kt new file mode 100644 index 00000000000..40799d77fbe --- /dev/null +++ b/services/networkmonitor/api/src/main/kotlin/io/element/android/services/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.services.networkmonitor.api + +enum class NetworkStatus { + Online, + Offline +} diff --git a/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/ui/ConnectivityIndicatorView.kt b/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/ui/ConnectivityIndicatorView.kt new file mode 100644 index 00000000000..e1b3ecc56e9 --- /dev/null +++ b/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/ui/ConnectivityIndicatorView.kt @@ -0,0 +1,104 @@ +/* + * 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.services.networkmonitor.api.ui + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.expandVertically +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.Box +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.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.ColorFilter +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 + +@Composable +fun ConnectivityIndicatorView( + isOnline: Boolean, + modifier: Modifier = Modifier +) { + // Display the network indicator with an animation + AnimatedVisibility( + visible = !isOnline, + enter = expandVertically(), + exit = shrinkVertically(), + ) { + 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 = "Offline", style = ElementTextStyles.Regular.bodyMD, color = tint) + } + } + + // Show missing status bar padding when the indicator is not visible + AnimatedVisibility( + visible = isOnline, + enter = expandVertically(), + exit = shrinkVertically(), + ) { + 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/services/networkmonitor/impl/build.gradle.kts b/services/networkmonitor/impl/build.gradle.kts new file mode 100644 index 00000000000..7ef10eb0635 --- /dev/null +++ b/services/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.services.networkmonitor.impl" +} + +dependencies { + implementation(libs.coroutines.core) + implementation(libs.dagger) + implementation(projects.libraries.core) + implementation(projects.libraries.di) + api(projects.services.networkmonitor.api) +} diff --git a/services/networkmonitor/impl/src/main/AndroidManifest.xml b/services/networkmonitor/impl/src/main/AndroidManifest.xml new file mode 100644 index 00000000000..c168cfccc7b --- /dev/null +++ b/services/networkmonitor/impl/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + + + + diff --git a/services/networkmonitor/impl/src/main/kotlin/io/element/android/services/networkmonitor/impl/NetworkMonitorImpl.kt b/services/networkmonitor/impl/src/main/kotlin/io/element/android/services/networkmonitor/impl/NetworkMonitorImpl.kt new file mode 100644 index 00000000000..0828e4bfb73 --- /dev/null +++ b/services/networkmonitor/impl/src/main/kotlin/io/element/android/services/networkmonitor/impl/NetworkMonitorImpl.kt @@ -0,0 +1,100 @@ +/* + * 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.services.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.libraries.di.AppScope +import io.element.android.libraries.di.ApplicationContext +import io.element.android.libraries.di.SingleIn +import io.element.android.services.networkmonitor.api.NetworkMonitor +import io.element.android.services.networkmonitor.api.NetworkStatus +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import timber.log.Timber +import javax.inject.Inject + +@ContributesBinding(scope = AppScope::class, boundType = NetworkMonitor::class) +@SingleIn(AppScope::class) +class NetworkMonitorImpl @Inject constructor( + @ApplicationContext private val context: Context +) : NetworkMonitor { + + private val connectivityManager: ConnectivityManager by lazy { + context.getSystemService(ConnectivityManager::class.java) + } + + private var callback: ConnectivityManager.NetworkCallback? = null + + private val _connectivity = MutableStateFlow(NetworkStatus.Online) + override val connectivity: Flow = _connectivity + + init { + subscribeToConnectionChanges() + } + + private fun subscribeToConnectionChanges() { + if (callback != null) return + + 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()}") + } + } + this.callback = callback + + connectivityManager.registerNetworkCallback( + NetworkRequest.Builder() + .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) + .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) + .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) + .build(), + 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/services/networkmonitor/test/build.gradle.kts b/services/networkmonitor/test/build.gradle.kts new file mode 100644 index 00000000000..8d9830af1b4 --- /dev/null +++ b/services/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.services.networkmonitor.test" +} + +dependencies { + api(projects.services.networkmonitor.api) + api(libs.coroutines.core) +} diff --git a/services/networkmonitor/test/src/main/kotlin/io/element/android/services/networkmonitor/test/FakeNetworkMonitor.kt b/services/networkmonitor/test/src/main/kotlin/io/element/android/services/networkmonitor/test/FakeNetworkMonitor.kt new file mode 100644 index 00000000000..a9a8a704c1c --- /dev/null +++ b/services/networkmonitor/test/src/main/kotlin/io/element/android/services/networkmonitor/test/FakeNetworkMonitor.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.services.networkmonitor.test + +import io.element.android.services.networkmonitor.api.NetworkMonitor +import io.element.android.services.networkmonitor.api.NetworkStatus +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +class FakeNetworkMonitor(override val connectivity: Flow = emptyFlow()) : NetworkMonitor 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 00000000000..a2726d8d7ff --- /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 00000000000..f2d984f169b --- /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 00000000000..ab965efff24 --- /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 00000000000..a7f9f94c2d6 --- /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 From 1aaf35defce19619686c524d364613e759f79416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 30 Mar 2023 19:13:46 +0200 Subject: [PATCH 2/5] Make `networkmonitor` a feature, not a service. --- features/messages/impl/build.gradle.kts | 4 ++-- .../android/features/messages/impl/MessagesPresenter.kt | 4 ++-- .../element/android/features/messages/impl/MessagesView.kt | 2 +- .../android/features/messages/MessagesPresenterTest.kt | 2 +- {services => features}/networkmonitor/api/build.gradle.kts | 2 +- .../android/features}/networkmonitor/api/NetworkMonitor.kt | 2 +- .../android/features}/networkmonitor/api/NetworkStatus.kt | 2 +- .../networkmonitor/api/ui/ConnectivityIndicatorView.kt | 3 +-- {services => features}/networkmonitor/impl/build.gradle.kts | 4 ++-- .../networkmonitor/impl/src/main/AndroidManifest.xml | 0 .../features}/networkmonitor/impl/NetworkMonitorImpl.kt | 6 +++--- {services => features}/networkmonitor/test/build.gradle.kts | 4 ++-- .../features}/networkmonitor/test/FakeNetworkMonitor.kt | 6 +++--- features/roomlist/impl/build.gradle.kts | 4 ++-- .../android/features/roomlist/impl/RoomListPresenter.kt | 4 ++-- .../element/android/features/roomlist/impl/RoomListView.kt | 2 +- .../features/roomlist/impl/RoomListPresenterTests.kt | 2 +- plugins/src/main/kotlin/extension/DependencyHandleScope.kt | 1 - samples/minimal/build.gradle.kts | 2 +- .../io/element/android/samples/minimal/RoomListScreen.kt | 2 +- 20 files changed, 28 insertions(+), 30 deletions(-) rename {services => features}/networkmonitor/api/build.gradle.kts (93%) rename {services/networkmonitor/api/src/main/kotlin/io/element/android/services => features/networkmonitor/api/src/main/kotlin/io/element/android/features}/networkmonitor/api/NetworkMonitor.kt (92%) rename {services/networkmonitor/api/src/main/kotlin/io/element/android/services => features/networkmonitor/api/src/main/kotlin/io/element/android/features}/networkmonitor/api/NetworkStatus.kt (92%) rename {services/networkmonitor/api/src/main/kotlin/io/element/android/services => features/networkmonitor/api/src/main/kotlin/io/element/android/features}/networkmonitor/api/ui/ConnectivityIndicatorView.kt (97%) rename {services => features}/networkmonitor/impl/build.gradle.kts (90%) rename {services => features}/networkmonitor/impl/src/main/AndroidManifest.xml (100%) rename {services/networkmonitor/impl/src/main/kotlin/io/element/android/services => features/networkmonitor/impl/src/main/kotlin/io/element/android/features}/networkmonitor/impl/NetworkMonitorImpl.kt (95%) rename {services => features}/networkmonitor/test/build.gradle.kts (88%) rename {services/networkmonitor/test/src/main/kotlin/io/element/android/services => features/networkmonitor/test/src/main/kotlin/io/element/android/features}/networkmonitor/test/FakeNetworkMonitor.kt (81%) diff --git a/features/messages/impl/build.gradle.kts b/features/messages/impl/build.gradle.kts index 592f0d8083f..355f36cce53 100644 --- a/features/messages/impl/build.gradle.kts +++ b/features/messages/impl/build.gradle.kts @@ -42,7 +42,7 @@ dependencies { implementation(projects.libraries.textcomposer) implementation(projects.libraries.uiStrings) implementation(projects.libraries.dateformatter.api) - implementation(projects.services.networkmonitor.api) + implementation(projects.features.networkmonitor.api) implementation(libs.coil.compose) implementation(libs.datetime) implementation(libs.accompanist.flowlayout) @@ -56,7 +56,7 @@ dependencies { testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.dateformatter.test) - testImplementation(projects.services.networkmonitor.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 f815d6a4e0b..f7ca0cb7cc7 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 @@ -39,8 +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.services.networkmonitor.api.NetworkMonitor -import io.element.android.services.networkmonitor.api.NetworkStatus +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 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 6a2eef27c9e..132d139174f 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.services.networkmonitor.api.ui.ConnectivityIndicatorView +import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView import kotlinx.coroutines.launch import timber.log.Timber 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 7a5fda1b2ca..a7bc112174f 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,12 +30,12 @@ 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 import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.textcomposer.MessageComposerMode -import io.element.android.services.networkmonitor.test.FakeNetworkMonitor import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest diff --git a/services/networkmonitor/api/build.gradle.kts b/features/networkmonitor/api/build.gradle.kts similarity index 93% rename from services/networkmonitor/api/build.gradle.kts rename to features/networkmonitor/api/build.gradle.kts index 2518a173e45..69eac5031af 100644 --- a/services/networkmonitor/api/build.gradle.kts +++ b/features/networkmonitor/api/build.gradle.kts @@ -21,7 +21,7 @@ plugins { } android { - namespace = "io.element.android.services.networkmonitor.api" + namespace = "io.element.android.features.networkmonitor.api" } dependencies { diff --git a/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkMonitor.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt similarity index 92% rename from services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkMonitor.kt rename to features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt index cf707791ced..9a46be85b06 100644 --- a/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkMonitor.kt +++ b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkMonitor.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.services.networkmonitor.api +package io.element.android.features.networkmonitor.api import kotlinx.coroutines.flow.Flow diff --git a/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkStatus.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt similarity index 92% rename from services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkStatus.kt rename to features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt index 40799d77fbe..4a2f012384e 100644 --- a/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/NetworkStatus.kt +++ b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/NetworkStatus.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.services.networkmonitor.api +package io.element.android.features.networkmonitor.api enum class NetworkStatus { Online, diff --git a/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/ui/ConnectivityIndicatorView.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt similarity index 97% rename from services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/ui/ConnectivityIndicatorView.kt rename to features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt index e1b3ecc56e9..0c6ea498197 100644 --- a/services/networkmonitor/api/src/main/kotlin/io/element/android/services/networkmonitor/api/ui/ConnectivityIndicatorView.kt +++ b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.services.networkmonitor.api.ui +package io.element.android.features.networkmonitor.api.ui import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.expandVertically @@ -22,7 +22,6 @@ 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.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth diff --git a/services/networkmonitor/impl/build.gradle.kts b/features/networkmonitor/impl/build.gradle.kts similarity index 90% rename from services/networkmonitor/impl/build.gradle.kts rename to features/networkmonitor/impl/build.gradle.kts index 7ef10eb0635..1eacdef6646 100644 --- a/services/networkmonitor/impl/build.gradle.kts +++ b/features/networkmonitor/impl/build.gradle.kts @@ -26,7 +26,7 @@ anvil { } android { - namespace = "io.element.android.services.networkmonitor.impl" + namespace = "io.element.android.features.networkmonitor.impl" } dependencies { @@ -34,5 +34,5 @@ dependencies { implementation(libs.dagger) implementation(projects.libraries.core) implementation(projects.libraries.di) - api(projects.services.networkmonitor.api) + api(projects.features.networkmonitor.api) } diff --git a/services/networkmonitor/impl/src/main/AndroidManifest.xml b/features/networkmonitor/impl/src/main/AndroidManifest.xml similarity index 100% rename from services/networkmonitor/impl/src/main/AndroidManifest.xml rename to features/networkmonitor/impl/src/main/AndroidManifest.xml diff --git a/services/networkmonitor/impl/src/main/kotlin/io/element/android/services/networkmonitor/impl/NetworkMonitorImpl.kt b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt similarity index 95% rename from services/networkmonitor/impl/src/main/kotlin/io/element/android/services/networkmonitor/impl/NetworkMonitorImpl.kt rename to features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt index 0828e4bfb73..8265fca40c3 100644 --- a/services/networkmonitor/impl/src/main/kotlin/io/element/android/services/networkmonitor/impl/NetworkMonitorImpl.kt +++ b/features/networkmonitor/impl/src/main/kotlin/io/element/android/features/networkmonitor/impl/NetworkMonitorImpl.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.services.networkmonitor.impl +package io.element.android.features.networkmonitor.impl import android.content.Context import android.net.ConnectivityManager @@ -25,8 +25,8 @@ import com.squareup.anvil.annotations.ContributesBinding import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.SingleIn -import io.element.android.services.networkmonitor.api.NetworkMonitor -import io.element.android.services.networkmonitor.api.NetworkStatus +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 import timber.log.Timber diff --git a/services/networkmonitor/test/build.gradle.kts b/features/networkmonitor/test/build.gradle.kts similarity index 88% rename from services/networkmonitor/test/build.gradle.kts rename to features/networkmonitor/test/build.gradle.kts index 8d9830af1b4..4feb8377803 100644 --- a/services/networkmonitor/test/build.gradle.kts +++ b/features/networkmonitor/test/build.gradle.kts @@ -21,10 +21,10 @@ plugins { } android { - namespace = "io.element.android.services.networkmonitor.test" + namespace = "io.element.android.features.networkmonitor.test" } dependencies { - api(projects.services.networkmonitor.api) + api(projects.features.networkmonitor.api) api(libs.coroutines.core) } diff --git a/services/networkmonitor/test/src/main/kotlin/io/element/android/services/networkmonitor/test/FakeNetworkMonitor.kt b/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt similarity index 81% rename from services/networkmonitor/test/src/main/kotlin/io/element/android/services/networkmonitor/test/FakeNetworkMonitor.kt rename to features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt index a9a8a704c1c..fa73d22ab37 100644 --- a/services/networkmonitor/test/src/main/kotlin/io/element/android/services/networkmonitor/test/FakeNetworkMonitor.kt +++ b/features/networkmonitor/test/src/main/kotlin/io/element/android/features/networkmonitor/test/FakeNetworkMonitor.kt @@ -14,10 +14,10 @@ * limitations under the License. */ -package io.element.android.services.networkmonitor.test +package io.element.android.features.networkmonitor.test -import io.element.android.services.networkmonitor.api.NetworkMonitor -import io.element.android.services.networkmonitor.api.NetworkStatus +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.emptyFlow diff --git a/features/roomlist/impl/build.gradle.kts b/features/roomlist/impl/build.gradle.kts index 6a78281ee6c..69f523e06eb 100644 --- a/features/roomlist/impl/build.gradle.kts +++ b/features/roomlist/impl/build.gradle.kts @@ -49,7 +49,7 @@ dependencies { implementation(projects.libraries.testtags) implementation(projects.libraries.uiStrings) implementation(projects.libraries.dateformatter.api) - implementation(projects.services.networkmonitor.api) + implementation(projects.features.networkmonitor.api) implementation(libs.accompanist.placeholder) api(projects.features.roomlist.api) ksp(libs.showkase.processor) @@ -62,7 +62,7 @@ dependencies { testImplementation(libs.test.robolectric) testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.dateformatter.test) - testImplementation(projects.services.networkmonitor.test) + testImplementation(projects.features.networkmonitor.test) 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 016850c76b9..1cc49baea75 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 @@ -41,8 +42,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.ui.model.MatrixUser -import io.element.android.services.networkmonitor.api.NetworkMonitor -import io.element.android.services.networkmonitor.api.NetworkStatus +import io.element.android.features.networkmonitor.api.NetworkStatus import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList 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 c67a7f55974..788fc561b47 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 @@ -67,7 +67,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.services.networkmonitor.api.ui.ConnectivityIndicatorView +import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView import io.element.android.libraries.designsystem.R as DrawableR import io.element.android.libraries.ui.strings.R as StringR 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 86cc2e27cb2..b8b3e41e5ad 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 @@ -37,7 +38,6 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService -import io.element.android.services.networkmonitor.test.FakeNetworkMonitor import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index e157acaf73f..314421ebc85 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -84,7 +84,6 @@ fun DependencyHandlerScope.allLibrariesImpl() { fun DependencyHandlerScope.allServicesImpl() { implementation(project(":services:analytics:noop")) implementation(project(":services:appnavstate:impl")) - implementation(project(":services:networkmonitor:impl")) implementation(project(":services:toolbox:impl")) } diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts index 4815cfff85d..91a605493ec 100644 --- a/samples/minimal/build.gradle.kts +++ b/samples/minimal/build.gradle.kts @@ -56,7 +56,7 @@ dependencies { implementation(projects.libraries.dateformatter.impl) implementation(projects.features.roomlist.impl) implementation(projects.features.login.impl) - implementation(projects.services.networkmonitor.impl) + implementation(projects.features.networkmonitor.impl) implementation(libs.coroutines.core) coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2") } 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 8c5b45da6ec..da9356b0a2d 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 @@ -28,7 +29,6 @@ import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimesta import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.services.networkmonitor.impl.NetworkMonitorImpl import kotlinx.coroutines.launch import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone From d0765f7dc9548c7488dee7892045f60c1fc45ad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 30 Mar 2023 19:14:29 +0200 Subject: [PATCH 3/5] Add changelog --- changelog.d/141.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog.d/141.feature diff --git a/changelog.d/141.feature b/changelog.d/141.feature new file mode 100644 index 00000000000..3cf75bd4846 --- /dev/null +++ b/changelog.d/141.feature @@ -0,0 +1 @@ +Add a `NetworkMonitor` component to track the network connection status From a9940e4d436c314d7065d29a7b9d80d1c311d2f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 5 Apr 2023 11:27:43 +0200 Subject: [PATCH 4/5] Fix hardcoded word --- features/networkmonitor/api/build.gradle.kts | 1 + .../networkmonitor/api/ui/ConnectivityIndicatorView.kt | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/features/networkmonitor/api/build.gradle.kts b/features/networkmonitor/api/build.gradle.kts index 69eac5031af..a9e6ed732db 100644 --- a/features/networkmonitor/api/build.gradle.kts +++ b/features/networkmonitor/api/build.gradle.kts @@ -27,4 +27,5 @@ android { 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/ui/ConnectivityIndicatorView.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt index 0c6ea498197..5135ce96a92 100644 --- 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 @@ -37,12 +37,14 @@ import androidx.compose.runtime.Composable 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( @@ -72,7 +74,7 @@ fun ConnectivityIndicatorView( modifier = Modifier.size(16.dp), ) Spacer(modifier = Modifier.width(8.dp)) - Text(text = "Offline", style = ElementTextStyles.Regular.bodyMD, color = tint) + Text(text = stringResource(StringR.string.common_offline), style = ElementTextStyles.Regular.bodyMD, color = tint) } } From 3dbc1739169aaa1f6c466479a043d40ff771b6d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Sat, 15 Apr 2023 20:53:34 +0200 Subject: [PATCH 5/5] Address review comments --- .../messages/impl/MessagesPresenter.kt | 3 +- .../networkmonitor/api/NetworkMonitor.kt | 1 + .../api/ui/ConnectivityIndicatorView.kt | 72 +++++++++++------- .../networkmonitor/impl/NetworkMonitorImpl.kt | 75 +++++++++---------- .../networkmonitor/test/FakeNetworkMonitor.kt | 10 ++- .../roomlist/impl/RoomListPresenter.kt | 2 +- .../features/roomlist/impl/RoomListView.kt | 1 - .../impl/components/RoomListTopBar.kt | 1 - 8 files changed, 92 insertions(+), 73 deletions(-) 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 f7ca0cb7cc7..94edf64d58d 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 @@ -69,7 +69,8 @@ class MessagesPresenter @Inject constructor( mutableStateOf(null) } - val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = NetworkStatus.Online) + val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = networkMonitor.currentConnectivityStatus) + println(networkConnectionStatus) LaunchedEffect(syncUpdateFlow) { roomAvatar.value = 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 index 9a46be85b06..e85a61512dd 100644 --- 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 @@ -20,4 +20,5 @@ 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/ui/ConnectivityIndicatorView.kt b/features/networkmonitor/api/src/main/kotlin/io/element/android/features/networkmonitor/api/ui/ConnectivityIndicatorView.kt index 5135ce96a92..6603f8178bc 100644 --- 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 @@ -17,7 +17,10 @@ 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 @@ -34,6 +37,10 @@ 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 @@ -51,43 +58,56 @@ 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( - visible = !isOnline, - enter = expandVertically(), - exit = shrinkVertically(), + visibleState = isIndicatorVisible, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), ) { - 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) - } + Indicator(modifier) } // Show missing status bar padding when the indicator is not visible AnimatedVisibility( - visible = isOnline, - enter = expandVertically(), - exit = shrinkVertically(), + visibleState = isStatusBarPaddingVisible, + enter = fadeIn() + expandVertically(), + exit = fadeOut() + shrinkVertically(), ) { - Spacer(modifier = Modifier.statusBarsPadding()) + 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() { 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 index 8265fca40c3..ba4d6c2775f 100644 --- 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 @@ -22,67 +22,60 @@ 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 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 import timber.log.Timber import javax.inject.Inject -@ContributesBinding(scope = AppScope::class, boundType = NetworkMonitor::class) +@ContributesBinding(scope = AppScope::class) @SingleIn(AppScope::class) class NetworkMonitorImpl @Inject constructor( - @ApplicationContext private val context: Context + @ApplicationContext context: Context ) : NetworkMonitor { - private val connectivityManager: ConnectivityManager by lazy { - context.getSystemService(ConnectivityManager::class.java) - } + private val connectivityManager: ConnectivityManager = context.getSystemService(ConnectivityManager::class.java) - private var callback: ConnectivityManager.NetworkCallback? = null + private val callback = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + _connectivity.value = connectivityManager.currentConnectionStatus() + Timber.v("Connectivity status (available): ${connectivityManager.currentConnectionStatus()}") + } - private val _connectivity = MutableStateFlow(NetworkStatus.Online) - override val connectivity: Flow = _connectivity + override fun onLost(network: Network) { + _connectivity.value = connectivityManager.currentConnectionStatus() + Timber.v("Connectivity status (lost): ${connectivityManager.currentConnectionStatus()}") + } - init { - subscribeToConnectionChanges() + override fun onCapabilitiesChanged( + network: Network, + networkCapabilities: NetworkCapabilities + ) { + _connectivity.value = connectivityManager.currentConnectionStatus() + Timber.v("Connectivity status (changed): ${connectivityManager.currentConnectionStatus()}") + } } - private fun subscribeToConnectionChanges() { - if (callback != null) return - - val callback = object : ConnectivityManager.NetworkCallback() { - override fun onAvailable(network: Network) { - _connectivity.value = connectivityManager.currentConnectionStatus() - Timber.v("Connectivity status (available): ${connectivityManager.currentConnectionStatus()}") - } + private val _connectivity = MutableStateFlow(NetworkStatus.Online) + override val connectivity: Flow = _connectivity - override fun onLost(network: Network) { - _connectivity.value = connectivityManager.currentConnectionStatus() - Timber.v("Connectivity status (lost): ${connectivityManager.currentConnectionStatus()}") - } + override val currentConnectivityStatus: NetworkStatus get() = _connectivity.value - override fun onCapabilitiesChanged( - network: Network, - networkCapabilities: NetworkCapabilities - ) { - _connectivity.value = connectivityManager.currentConnectionStatus() - Timber.v("Connectivity status (changed): ${connectivityManager.currentConnectionStatus()}") - } - } - this.callback = callback + init { + listenToConnectionChanges() + } - connectivityManager.registerNetworkCallback( - NetworkRequest.Builder() - .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) - .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build(), - callback - ) + 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()) } 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 index fa73d22ab37..d7ccab0b267 100644 --- 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 @@ -19,6 +19,12 @@ 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.emptyFlow +import kotlinx.coroutines.flow.MutableStateFlow -class FakeNetworkMonitor(override val connectivity: Flow = emptyFlow()) : NetworkMonitor +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/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 d3a5a242d6f..03524160a28 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 @@ -74,7 +74,7 @@ class RoomListPresenter @Inject constructor( .roomSummaries() .collectAsState() - val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = NetworkStatus.Online) + val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = networkMonitor.currentConnectivityStatus) Timber.v("RoomSummaries size = ${roomSummaries.size}") 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 0ad9ed8f653..d39feeac363 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 @@ -159,7 +159,6 @@ fun RoomListContent( onFilterChanged = { state.eventSink(RoomListEvents.UpdateFilter(it)) }, onOpenSettings = onOpenSettings, scrollBehavior = scrollBehavior, - useStatusBarInsets = state.hasNetworkConnection, ) } }, 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 198280a24ca..f9bdefd95e0 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 @@ -73,7 +73,6 @@ fun RoomListTopBar( onFilterChanged: (String) -> Unit, onOpenSettings: () -> Unit, scrollBehavior: TopAppBarScrollBehavior, - useStatusBarInsets: Boolean, modifier: Modifier = Modifier, ) { LogCompositions(