diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt index 50bc09d0a0..8910cc3976 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInPresenter.kt @@ -20,6 +20,7 @@ import android.Manifest import android.os.Build import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.permissions.api.PermissionsPresenter @@ -52,6 +53,7 @@ class LoggedInPresenter @Inject constructor( pushService.registerWith(matrixClient, pushProvider, distributor) } + val syncState = matrixClient.syncService().syncState.collectAsState() val permissionsState = postNotificationPermissionsPresenter.present() // fun handleEvents(event: LoggedInEvents) { @@ -60,6 +62,7 @@ class LoggedInPresenter @Inject constructor( // } return LoggedInState( + syncState = syncState.value, permissionsState = permissionsState, // eventSink = ::handleEvents ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt index 8cf8060981..075242cddb 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInState.kt @@ -16,9 +16,11 @@ package io.element.android.appnav.loggedin +import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.permissions.api.PermissionsState data class LoggedInState( + val syncState: SyncState, val permissionsState: PermissionsState, // val eventSink: (LoggedInEvents) -> Unit ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt index b131d6d610..533cc15e03 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInStateProvider.kt @@ -17,17 +17,22 @@ package io.element.android.appnav.loggedin import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.permissions.api.createDummyPostNotificationPermissionsState open class LoggedInStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aLoggedInState(), + aLoggedInState(syncState = SyncState.Idle), // Add other state here ) } -fun aLoggedInState() = LoggedInState( +fun aLoggedInState( + syncState: SyncState = SyncState.Syncing, +) = LoggedInState( + syncState = syncState, permissionsState = createDummyPostNotificationPermissionsState(), // eventSink = {} ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt index dc1faa0e2d..60784ea4ed 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/LoggedInView.kt @@ -16,14 +16,19 @@ package io.element.android.appnav.loggedin +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.unit.dp import io.element.android.libraries.androidutils.system.openAppSettingsPage -import io.element.android.libraries.designsystem.preview.ElementPreviewDark -import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.permissions.api.PermissionsView @Composable @@ -33,25 +38,27 @@ fun LoggedInView( ) { val context = LocalContext.current - PermissionsView( - state = state.permissionsState, - modifier = modifier, - openSystemSettings = context::openAppSettingsPage - ) + Box( + modifier = modifier + .fillMaxSize() + .systemBarsPadding() + ) { + SyncStateView( + modifier = Modifier + .padding(top = 8.dp) + .align(Alignment.TopCenter), + syncState = state.syncState, + ) + PermissionsView( + state = state.permissionsState, + openSystemSettings = context::openAppSettingsPage + ) + } } -@Preview +@DayNightPreviews @Composable -fun LoggedInViewLightPreview(@PreviewParameter(LoggedInStateProvider::class) state: LoggedInState) = - ElementPreviewLight { ContentToPreview(state) } - -@Preview -@Composable -fun LoggedInViewDarkPreview(@PreviewParameter(LoggedInStateProvider::class) state: LoggedInState) = - ElementPreviewDark { ContentToPreview(state) } - -@Composable -private fun ContentToPreview(state: LoggedInState) { +fun LoggedInViewPreview(@PreviewParameter(LoggedInStateProvider::class) state: LoggedInState) = ElementPreview { LoggedInView( state = state ) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt new file mode 100644 index 0000000000..0c147fe457 --- /dev/null +++ b/appnav/src/main/kotlin/io/element/android/appnav/loggedin/SyncStateView.kt @@ -0,0 +1,101 @@ +/* + * 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.appnav.loggedin + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.spring +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +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.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.progressSemantics +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.element.android.libraries.designsystem.preview.DayNightPreviews +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.Surface +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.matrix.api.sync.SyncState +import io.element.android.libraries.theme.ElementTheme +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun SyncStateView( + syncState: SyncState, + modifier: Modifier = Modifier +) { + val animationSpec = spring(stiffness = 500F) + AnimatedVisibility( + modifier = modifier, + visible = syncState.mustBeVisible(), + enter = fadeIn(animationSpec = animationSpec), + exit = fadeOut(animationSpec = animationSpec), + ) { + Surface( + shape = RoundedCornerShape(24.dp), + shadowElevation = 8.dp, + ) { + Row( + modifier = Modifier + .background(color = ElementTheme.colors.bgSubtleSecondary) + .padding(horizontal = 24.dp, vertical = 10.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(10.dp) + ) { + CircularProgressIndicator( + modifier = Modifier + .progressSemantics() + .size(12.dp), + color = ElementTheme.colors.textPrimary, + strokeWidth = 1.5.dp, + ) + Text( + text = stringResource(id = CommonStrings.common_syncing), + color = ElementTheme.colors.textPrimary, + style = ElementTheme.typography.fontBodyMdMedium + ) + } + } + } +} + +private fun SyncState.mustBeVisible() = when (this) { + SyncState.Idle -> true + SyncState.Syncing -> false + SyncState.InError -> false /* In this case, the network error banner can be displayed */ + SyncState.Terminated -> false +} + +@DayNightPreviews +@Composable +fun SyncStateViewPreview() = ElementPreview { + // Add a box to see the shadow + Box(modifier = Modifier.padding(24.dp)) { + SyncStateView( + syncState = SyncState.Idle + ) + } +}