Add a inderminate progress bar when loging out and in Waiting state. (#4538)

* Add a check network connection when the Waiting state last too long.

* Update screenshots

---------

Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
Benoit Marty
2025-04-07 11:58:02 +02:00
committed by GitHub
parent 60ef6c6d89
commit 44f0c23149
9 changed files with 100 additions and 17 deletions

View File

@@ -15,6 +15,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter
@@ -25,6 +26,7 @@ import io.element.android.libraries.matrix.api.encryption.BackupState
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
@@ -44,6 +46,16 @@ class LogoutPresenter @Inject constructor(
}
.collectAsState(initial = BackupUploadState.Unknown)
var waitingForALongTime by remember { mutableStateOf(false) }
LaunchedEffect(backupUploadState) {
if (backupUploadState is BackupUploadState.Waiting) {
delay(2_000)
waitingForALongTime = true
} else {
waitingForALongTime = false
}
}
val isLastDevice by encryptionService.isLastDevice.collectAsState()
val backupState by encryptionService.backupStateStateFlow.collectAsState()
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState()
@@ -79,6 +91,7 @@ class LogoutPresenter @Inject constructor(
doesBackupExistOnServer = doesBackupExistOnServerAction.value.dataOrNull().orTrue(),
recoveryState = recoveryState,
backupUploadState = backupUploadState,
waitingForALongTime = waitingForALongTime,
logoutAction = logoutAction.value,
eventSink = ::handleEvents
)

View File

@@ -18,6 +18,7 @@ data class LogoutState(
val doesBackupExistOnServer: Boolean,
val recoveryState: RecoveryState,
val backupUploadState: BackupUploadState,
val waitingForALongTime: Boolean,
val logoutAction: AsyncAction<Unit>,
val eventSink: (LogoutEvents) -> Unit,
)

View File

@@ -29,6 +29,15 @@ open class LogoutStateProvider : PreviewParameterProvider<LogoutState> {
aLogoutState(isLastDevice = true, recoveryState = RecoveryState.DISABLED),
// Last session no backup
aLogoutState(isLastDevice = true, backupState = BackupState.UNKNOWN, doesBackupExistOnServer = false),
aLogoutState(
isLastDevice = false,
backupUploadState = BackupUploadState.Waiting,
),
aLogoutState(
isLastDevice = false,
backupUploadState = BackupUploadState.Waiting,
waitingForALongTime = true,
),
)
}
@@ -38,6 +47,7 @@ fun aLogoutState(
doesBackupExistOnServer: Boolean = true,
recoveryState: RecoveryState = RecoveryState.ENABLED,
backupUploadState: BackupUploadState = BackupUploadState.Unknown,
waitingForALongTime: Boolean = false,
logoutAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (LogoutEvents) -> Unit = {},
) = LogoutState(
@@ -46,6 +56,7 @@ fun aLogoutState(
doesBackupExistOnServer = doesBackupExistOnServer,
recoveryState = recoveryState,
backupUploadState = backupUploadState,
waitingForALongTime = waitingForALongTime,
logoutAction = logoutAction,
eventSink = eventSink,
)

View File

@@ -143,24 +143,41 @@ private fun ColumnScope.Buttons(
@Composable
private fun Content(
state: LogoutState,
modifier: Modifier = Modifier,
) {
if (state.backupUploadState is BackupUploadState.Uploading) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(top = 60.dp, start = 20.dp, end = 20.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
progress = { state.backupUploadState.backedUpCount.toFloat() / state.backupUploadState.totalCount.toFloat() },
trackColor = ElementTheme.colors.progressIndicatorTrackColor,
)
Text(
modifier = Modifier.align(Alignment.End),
text = "${state.backupUploadState.backedUpCount} / ${state.backupUploadState.totalCount}",
style = ElementTheme.typography.fontBodySmRegular,
)
Column(
modifier = modifier
.fillMaxWidth()
.padding(top = 60.dp, start = 20.dp, end = 20.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
when (state.backupUploadState) {
is BackupUploadState.Uploading -> {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
progress = { state.backupUploadState.backedUpCount.toFloat() / state.backupUploadState.totalCount.toFloat() },
trackColor = ElementTheme.colors.progressIndicatorTrackColor,
)
Text(
modifier = Modifier.align(Alignment.End),
text = "${state.backupUploadState.backedUpCount} / ${state.backupUploadState.totalCount}",
style = ElementTheme.typography.fontBodySmRegular,
)
}
BackupUploadState.Waiting -> {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth(),
trackColor = ElementTheme.colors.progressIndicatorTrackColor,
)
if (state.waitingForALongTime) {
Text(
modifier = Modifier.align(Alignment.CenterHorizontally),
text = stringResource(CommonStrings.common_please_check_internet_connection),
style = ElementTheme.typography.fontBodySmRegular,
)
}
}
else -> Unit
}
}
}

View File

@@ -44,6 +44,7 @@ class LogoutPresenterTest {
assertThat(initialState.doesBackupExistOnServer).isTrue()
assertThat(initialState.recoveryState).isEqualTo(RecoveryState.UNKNOWN)
assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown)
assertThat(initialState.waitingForALongTime).isFalse()
assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized)
}
}
@@ -66,6 +67,34 @@ class LogoutPresenterTest {
}
}
@Test
fun `present - initial state - waiting a long time`() = runTest {
val encryptionService = FakeEncryptionService()
encryptionService.givenWaitForBackupUploadSteadyStateFlow(
flow {
emit(BackupUploadState.Waiting)
delay(3_000)
}
)
val presenter = createLogoutPresenter(
encryptionService = encryptionService
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.waitingForALongTime).isFalse()
assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown)
val waitingState = awaitItem()
assertThat(waitingState.backupUploadState).isEqualTo(BackupUploadState.Waiting)
assertThat(initialState.waitingForALongTime).isFalse()
skipItems(1)
val waitingALongTimeState = awaitItem()
assertThat(waitingALongTimeState.backupUploadState).isEqualTo(BackupUploadState.Waiting)
assertThat(waitingALongTimeState.waitingForALongTime).isTrue()
}
}
@Test
fun `present - initial state - backing up`() = runTest {
val encryptionService = FakeEncryptionService()