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.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter 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.BackupUploadState
import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.EncryptionService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -44,6 +46,16 @@ class LogoutPresenter @Inject constructor(
} }
.collectAsState(initial = BackupUploadState.Unknown) .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 isLastDevice by encryptionService.isLastDevice.collectAsState()
val backupState by encryptionService.backupStateStateFlow.collectAsState() val backupState by encryptionService.backupStateStateFlow.collectAsState()
val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState() val recoveryState by encryptionService.recoveryStateStateFlow.collectAsState()
@ -79,6 +91,7 @@ class LogoutPresenter @Inject constructor(
doesBackupExistOnServer = doesBackupExistOnServerAction.value.dataOrNull().orTrue(), doesBackupExistOnServer = doesBackupExistOnServerAction.value.dataOrNull().orTrue(),
recoveryState = recoveryState, recoveryState = recoveryState,
backupUploadState = backupUploadState, backupUploadState = backupUploadState,
waitingForALongTime = waitingForALongTime,
logoutAction = logoutAction.value, logoutAction = logoutAction.value,
eventSink = ::handleEvents eventSink = ::handleEvents
) )

View file

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

View file

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

View file

@ -143,24 +143,41 @@ private fun ColumnScope.Buttons(
@Composable @Composable
private fun Content( private fun Content(
state: LogoutState, state: LogoutState,
modifier: Modifier = Modifier,
) { ) {
if (state.backupUploadState is BackupUploadState.Uploading) { Column(
Column( modifier = modifier
modifier = Modifier .fillMaxWidth()
.fillMaxWidth() .padding(top = 60.dp, start = 20.dp, end = 20.dp),
.padding(top = 60.dp, start = 20.dp, end = 20.dp), verticalArrangement = Arrangement.spacedBy(8.dp)
verticalArrangement = Arrangement.spacedBy(8.dp) ) {
) { when (state.backupUploadState) {
LinearProgressIndicator( is BackupUploadState.Uploading -> {
modifier = Modifier.fillMaxWidth(), LinearProgressIndicator(
progress = { state.backupUploadState.backedUpCount.toFloat() / state.backupUploadState.totalCount.toFloat() }, modifier = Modifier.fillMaxWidth(),
trackColor = ElementTheme.colors.progressIndicatorTrackColor, progress = { state.backupUploadState.backedUpCount.toFloat() / state.backupUploadState.totalCount.toFloat() },
) trackColor = ElementTheme.colors.progressIndicatorTrackColor,
Text( )
modifier = Modifier.align(Alignment.End), Text(
text = "${state.backupUploadState.backedUpCount} / ${state.backupUploadState.totalCount}", modifier = Modifier.align(Alignment.End),
style = ElementTheme.typography.fontBodySmRegular, 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.doesBackupExistOnServer).isTrue()
assertThat(initialState.recoveryState).isEqualTo(RecoveryState.UNKNOWN) assertThat(initialState.recoveryState).isEqualTo(RecoveryState.UNKNOWN)
assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown) assertThat(initialState.backupUploadState).isEqualTo(BackupUploadState.Unknown)
assertThat(initialState.waitingForALongTime).isFalse()
assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized) 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 @Test
fun `present - initial state - backing up`() = runTest { fun `present - initial state - backing up`() = runTest {
val encryptionService = FakeEncryptionService() 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