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 f335eb1106
commit b7abdb042f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
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()

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b005a634331a485e2f80fb773a147207c782eaf2d923d1a87c5b45597309c658
size 25794

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d800ec0c02e8e2f8fff755d12d2e657d6fbae10a8b9f070bb627b3ccf9d3e159
size 30566

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:27587909106650e33d7bc7854c0f2dd7ca6e2dad0aaf6487bf266753288ec6f6
size 24898

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9e65635400bfda5d242d07ebe31113e3ef2d039f21208421023f099997d5e4f9
size 29603