diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt index 29cb00824bd..2a40d0e0305 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBar.kt @@ -37,13 +37,19 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +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.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.IntSize import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.wire.android.BuildConfig import com.wire.android.R import com.wire.android.ui.theme.ThemeOption import com.wire.android.ui.theme.WireTheme @@ -52,6 +58,7 @@ import com.wire.android.ui.theme.wireDimensions import com.wire.android.ui.theme.wireTypography import com.wire.android.util.ui.PreviewMultipleThemes import com.wire.kalium.logic.data.id.ConversationId +import com.wire.kalium.network.NetworkState @Composable fun CommonTopAppBar( @@ -65,6 +72,7 @@ fun CommonTopAppBar( Column(modifier = modifier) { ConnectivityStatusBar( themeOption = themeOption, + networkState = commonTopAppBarState.networkState, connectivityInfo = commonTopAppBarState.connectivityState, onReturnToCallClick = onReturnToCallClick, onReturnToIncomingCallClick = onReturnToIncomingCallClick, @@ -79,7 +87,10 @@ fun getBackgroundColor(connectivityInfo: ConnectivityUIState): Color { is ConnectivityUIState.EstablishedCall, is ConnectivityUIState.IncomingCall, is ConnectivityUIState.OutgoingCall -> MaterialTheme.wireColorScheme.positive - ConnectivityUIState.Connecting, ConnectivityUIState.WaitingConnection -> MaterialTheme.wireColorScheme.primary + + is ConnectivityUIState.WaitingConnection, + ConnectivityUIState.Connecting -> MaterialTheme.wireColorScheme.primary + ConnectivityUIState.None -> MaterialTheme.wireColorScheme.background } } @@ -88,6 +99,7 @@ fun getBackgroundColor(connectivityInfo: ConnectivityUIState): Color { private fun ConnectivityStatusBar( themeOption: ThemeOption, connectivityInfo: ConnectivityUIState, + networkState: NetworkState, onReturnToCallClick: (ConnectivityUIState.EstablishedCall) -> Unit, onReturnToIncomingCallClick: (ConnectivityUIState.IncomingCall) -> Unit, onReturnToOutgoingCallClick: (ConnectivityUIState.OutgoingCall) -> Unit @@ -119,14 +131,23 @@ private fun ConnectivityStatusBar( .background(backgroundColor) .run { when (connectivityInfo) { - is ConnectivityUIState.EstablishedCall -> - clickable(onClick = { onReturnToCallClick(connectivityInfo) }) + is ConnectivityUIState.EstablishedCall -> clickable(onClick = { + onReturnToCallClick( + connectivityInfo + ) + }) - is ConnectivityUIState.IncomingCall -> - clickable(onClick = { onReturnToIncomingCallClick(connectivityInfo) }) + is ConnectivityUIState.IncomingCall -> clickable(onClick = { + onReturnToIncomingCallClick( + connectivityInfo + ) + }) - is ConnectivityUIState.OutgoingCall -> - clickable(onClick = { onReturnToOutgoingCallClick(connectivityInfo) }) + is ConnectivityUIState.OutgoingCall -> clickable(onClick = { + onReturnToOutgoingCallClick( + connectivityInfo + ) + }) else -> this } @@ -158,11 +179,22 @@ private fun ConnectivityStatusBar( MaterialTheme.wireColorScheme.onPrimary ) - ConnectivityUIState.WaitingConnection -> - StatusLabel( - R.string.connectivity_status_bar_waiting_for_network, - MaterialTheme.wireColorScheme.onPrimary - ) + is ConnectivityUIState.WaitingConnection -> { + val color = MaterialTheme.wireColorScheme.onPrimary + val waitingStatus: @Composable () -> Unit = { + StatusLabel( + stringResource = R.string.connectivity_status_bar_waiting_for_network, + color + ) + } + + if (!BuildConfig.PRIVATE_BUILD) { + waitingStatus() + return@Column + } + + WaitingStatusLabelInternal(connectivityInfo, networkState, waitingStatus) + } ConnectivityUIState.None -> {} } @@ -170,6 +202,40 @@ private fun ConnectivityStatusBar( } } +@Composable +private fun WaitingStatusLabelInternal( + connectivityInfo: ConnectivityUIState.WaitingConnection, + networkState: NetworkState, + waitingStatus: @Composable () -> Unit, +) { + assert(BuildConfig.PRIVATE_BUILD) { "This composable should only be used in the internal versions" } + + val cause = connectivityInfo.cause?.javaClass?.simpleName ?: "null" + val delay = connectivityInfo.retryDelay ?: "null" + var fontSize by remember { mutableStateOf(1f) } + + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + waitingStatus() + Text( + text = "Cause: $cause Delay: $delay, Net: $networkState", + style = MaterialTheme.wireTypography.title03.copy( + fontSize = MaterialTheme.wireTypography.title03.fontSize * fontSize, + color = MaterialTheme.wireColorScheme.onPrimary, + ), + onTextLayout = { + // This is used to make sure the text fits in the available space + // so no needed information is cut off. It introduces a small delay in the text + // rendering but it is not important as this code is only used in the debug version + if (it.hasVisualOverflow) { + fontSize *= 0.9f + } + }, + ) + } +} + @Composable private fun OngoingCallContent(isMuted: Boolean) { Row { @@ -208,11 +274,23 @@ private fun OutgoingCallContent(conversationName: String?) { private fun StatusLabel( stringResource: Int, color: Color = MaterialTheme.wireColorScheme.onPrimary +) { + StatusLabel( + string = stringResource(id = stringResource), + color = color, + ) +} + +@Composable +private fun StatusLabel( + string: String, + color: Color = MaterialTheme.wireColorScheme.onPrimary ) { Text( - text = stringResource(id = stringResource).uppercase(), + text = string.uppercase(), color = color, style = MaterialTheme.wireTypography.title03, + textAlign = TextAlign.Center, ) } @@ -294,7 +372,32 @@ fun PreviewCommonTopAppBar_ConnectivityCallNotMuted() = fun PreviewCommonTopAppBar_ConnectivityConnecting() = PreviewCommonTopAppBar(ConnectivityUIState.Connecting) +@PreviewMultipleThemes +@Composable +fun PreviewCommonTopAppBar_ConnectivityWaitingConnection() = + PreviewCommonTopAppBar(ConnectivityUIState.WaitingConnection(null, null)) + @PreviewMultipleThemes @Composable fun PreviewCommonTopAppBar_ConnectivityNone() = PreviewCommonTopAppBar(ConnectivityUIState.None) + +@PreviewMultipleThemes +@Composable +fun PreviewCommonTopAppBar_ConnectivityIncomingCall() = + PreviewCommonTopAppBar( + ConnectivityUIState.IncomingCall( + ConversationId("what", "ever"), + "callerName" + ) + ) + +@PreviewMultipleThemes +@Composable +fun PreviewCommonTopAppBar_ConnectivityOutgoingCall() = + PreviewCommonTopAppBar( + ConnectivityUIState.OutgoingCall( + ConversationId("what", "ever"), + "conversationName" + ) + ) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarState.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarState.kt index 64177c20502..7d98b8c6f2f 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarState.kt @@ -17,6 +17,9 @@ */ package com.wire.android.ui.common.topappbar +import com.wire.kalium.network.NetworkState + data class CommonTopAppBarState( val connectivityState: ConnectivityUIState = ConnectivityUIState.None, + val networkState: NetworkState = NetworkState.NotConnected, ) diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt index 8b34b8de34a..d311d288704 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModel.kt @@ -61,9 +61,10 @@ class CommonTopAppBarViewModel @Inject constructor( coreLogic.sessionScope(userId) { observeSyncState().map { when (it) { - is SyncState.Failed, SyncState.Waiting -> Connectivity.WAITING_CONNECTION - SyncState.GatheringPendingEvents, SyncState.SlowSync -> Connectivity.CONNECTING - SyncState.Live -> Connectivity.CONNECTED + SyncState.Waiting -> Connectivity.WaitingConnection(null, null) + is SyncState.Failed -> Connectivity.WaitingConnection(it.cause, it.retryDelay) + SyncState.GatheringPendingEvents, SyncState.SlowSync -> Connectivity.Connecting + SyncState.Live -> Connectivity.Connected } } } @@ -117,6 +118,9 @@ class CommonTopAppBarViewModel @Inject constructor( state = state.copy(connectivityState = connectivityUIState) } } + coreLogic.networkStateObserver.observeNetworkState().collectLatest { + state = state.copy(networkState = it) + } } } @@ -129,20 +133,38 @@ class CommonTopAppBarViewModel @Inject constructor( val canDisplayConnectivityIssues = currentScreen !is CurrentScreen.AuthRelated if (activeCall != null) { - return if (activeCall.status == CallStatus.INCOMING) { - ConnectivityUIState.IncomingCall(activeCall.conversationId, activeCall.callerName) - } else if (activeCall.status == CallStatus.STARTED) { - ConnectivityUIState.OutgoingCall(activeCall.conversationId, activeCall.conversationName) - } else { - ConnectivityUIState.EstablishedCall(activeCall.conversationId, activeCall.isMuted) + return when (activeCall.status) { + CallStatus.INCOMING -> { + ConnectivityUIState.IncomingCall( + activeCall.conversationId, + activeCall.callerName + ) + } + + CallStatus.STARTED -> { + ConnectivityUIState.OutgoingCall( + activeCall.conversationId, + activeCall.conversationName + ) + } + + else -> { + ConnectivityUIState.EstablishedCall( + activeCall.conversationId, + activeCall.isMuted + ) + } } } return if (canDisplayConnectivityIssues) { when (connectivity) { - Connectivity.WAITING_CONNECTION -> ConnectivityUIState.WaitingConnection - Connectivity.CONNECTING -> ConnectivityUIState.Connecting - Connectivity.CONNECTED -> ConnectivityUIState.None + Connectivity.Connecting -> ConnectivityUIState.Connecting + Connectivity.Connected -> ConnectivityUIState.None + is Connectivity.WaitingConnection -> ConnectivityUIState.WaitingConnection( + connectivity.cause, + connectivity.retryDelay, + ) } } else { ConnectivityUIState.None diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/Connectivity.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/Connectivity.kt index 8a94de485b1..645fdaee542 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/Connectivity.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/Connectivity.kt @@ -18,6 +18,11 @@ package com.wire.android.ui.common.topappbar -enum class Connectivity { - WAITING_CONNECTION, CONNECTING, CONNECTED +import com.wire.kalium.logic.CoreFailure +import kotlin.time.Duration + +sealed interface Connectivity { + data class WaitingConnection(val cause: CoreFailure?, val retryDelay: Duration?) : Connectivity + data object Connecting : Connectivity + data object Connected : Connectivity } diff --git a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt index f1ee16660cf..0730053e6d3 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/topappbar/ConnectivityUIState.kt @@ -19,13 +19,15 @@ package com.wire.android.ui.common.topappbar import androidx.compose.runtime.Stable +import com.wire.kalium.logic.CoreFailure import com.wire.kalium.logic.data.id.ConversationId +import kotlin.time.Duration @Stable sealed interface ConnectivityUIState { data object Connecting : ConnectivityUIState - data object WaitingConnection : ConnectivityUIState + data class WaitingConnection(val cause: CoreFailure?, val retryDelay: Duration?) : ConnectivityUIState data object None : ConnectivityUIState diff --git a/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt b/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt index 779213b8078..469783fd2e5 100644 --- a/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt +++ b/app/src/test/kotlin/com/wire/android/ui/common/topappbar/CommonTopAppBarViewModelTest.kt @@ -35,6 +35,8 @@ import com.wire.kalium.logic.feature.call.usecase.ObserveEstablishedCallsUseCase import com.wire.kalium.logic.feature.call.usecase.ObserveOutgoingCallUseCase import com.wire.kalium.logic.feature.session.CurrentSessionResult import com.wire.kalium.logic.sync.ObserveSyncStateUseCase +import com.wire.kalium.network.NetworkState +import com.wire.kalium.network.NetworkStateObserver import io.mockk.MockKAnnotations import io.mockk.coEvery import io.mockk.every @@ -264,6 +266,9 @@ class CommonTopAppBarViewModelTest { @MockK private lateinit var globalKaliumScope: GlobalKaliumScope + @MockK + private lateinit var networkStateObserver: NetworkStateObserver + init { MockKAnnotations.init(this) every { @@ -294,6 +299,14 @@ class CommonTopAppBarViewModelTest { coreLogic.getGlobalScope() } returns globalKaliumScope + every { + coreLogic.networkStateObserver + } returns networkStateObserver + + every { + networkStateObserver.observeNetworkState() + } returns MutableStateFlow(NetworkState.ConnectedWithInternet) + every { globalKaliumScope.session.currentSessionFlow() } returns emptyFlow() diff --git a/kalium b/kalium index fc3f3e3a123..e6d6c381356 160000 --- a/kalium +++ b/kalium @@ -1 +1 @@ -Subproject commit fc3f3e3a1232733132a9302fd4510c898325e936 +Subproject commit e6d6c381356b3f9aa66a886ffb5dd2b28cc79961