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:
parent
60ef6c6d89
commit
44f0c23149
9 changed files with 100 additions and 17 deletions
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:b005a634331a485e2f80fb773a147207c782eaf2d923d1a87c5b45597309c658
|
||||||
|
size 25794
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:d800ec0c02e8e2f8fff755d12d2e657d6fbae10a8b9f070bb627b3ccf9d3e159
|
||||||
|
size 30566
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:27587909106650e33d7bc7854c0f2dd7ca6e2dad0aaf6487bf266753288ec6f6
|
||||||
|
size 24898
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
version https://git-lfs.github.com/spec/v1
|
||||||
|
oid sha256:9e65635400bfda5d242d07ebe31113e3ef2d039f21208421023f099997d5e4f9
|
||||||
|
size 29603
|
||||||
Loading…
Add table
Add a link
Reference in a new issue