Benoit Marty
9 months ago
16 changed files with 477 additions and 52 deletions
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
/* |
||||
* 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.features.logout.api.direct |
||||
|
||||
sealed interface DirectLogoutEvents { |
||||
data class Logout(val ignoreSdkError: Boolean) : DirectLogoutEvents |
||||
data object CloseDialogs : DirectLogoutEvents |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
/* |
||||
* 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.features.logout.api.direct |
||||
|
||||
import io.element.android.libraries.architecture.Presenter |
||||
|
||||
interface DirectLogoutPresenter : Presenter<DirectLogoutState> |
@ -0,0 +1,26 @@
@@ -0,0 +1,26 @@
|
||||
/* |
||||
* 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.features.logout.api.direct |
||||
|
||||
import io.element.android.libraries.architecture.Async |
||||
|
||||
data class DirectLogoutState( |
||||
val canDoDirectSignOut: Boolean, |
||||
val showConfirmationDialog: Boolean, |
||||
val logoutAction: Async<String?>, |
||||
val eventSink: (DirectLogoutEvents) -> Unit, |
||||
) |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
/* |
||||
* 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.features.logout.api.direct |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
|
||||
interface DirectLogoutView { |
||||
@Composable |
||||
fun render( |
||||
state: DirectLogoutState, |
||||
onSuccessLogout: (logoutUrlResult: String?) -> Unit |
||||
) |
||||
} |
@ -0,0 +1,113 @@
@@ -0,0 +1,113 @@
|
||||
/* |
||||
* 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.features.logout.impl.direct |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.LaunchedEffect |
||||
import androidx.compose.runtime.MutableState |
||||
import androidx.compose.runtime.collectAsState |
||||
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 com.squareup.anvil.annotations.ContributesBinding |
||||
import io.element.android.features.logout.api.direct.DirectLogoutEvents |
||||
import io.element.android.features.logout.api.direct.DirectLogoutPresenter |
||||
import io.element.android.features.logout.api.direct.DirectLogoutState |
||||
import io.element.android.features.logout.impl.tools.isBackingUp |
||||
import io.element.android.libraries.architecture.Async |
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState |
||||
import io.element.android.libraries.di.SessionScope |
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService |
||||
import io.element.android.libraries.featureflag.api.FeatureFlags |
||||
import io.element.android.libraries.matrix.api.MatrixClient |
||||
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.flow.emptyFlow |
||||
import kotlinx.coroutines.flow.flowOf |
||||
import kotlinx.coroutines.launch |
||||
import javax.inject.Inject |
||||
|
||||
@ContributesBinding(SessionScope::class) |
||||
class DefaultDirectLogoutPresenter @Inject constructor( |
||||
private val matrixClient: MatrixClient, |
||||
private val encryptionService: EncryptionService, |
||||
private val featureFlagService: FeatureFlagService, |
||||
) : DirectLogoutPresenter { |
||||
@Composable |
||||
override fun present(): DirectLogoutState { |
||||
val localCoroutineScope = rememberCoroutineScope() |
||||
|
||||
val logoutAction: MutableState<Async<String?>> = remember { |
||||
mutableStateOf(Async.Uninitialized) |
||||
} |
||||
|
||||
val secureStorageFlag by featureFlagService.isFeatureEnabledFlow(FeatureFlags.SecureStorage) |
||||
.collectAsState(initial = null) |
||||
|
||||
val backupUploadState: BackupUploadState by remember(secureStorageFlag) { |
||||
when (secureStorageFlag) { |
||||
true -> encryptionService.waitForBackupUploadSteadyState() |
||||
false -> flowOf(BackupUploadState.Done) |
||||
else -> emptyFlow() |
||||
} |
||||
} |
||||
.collectAsState(initial = BackupUploadState.Unknown) |
||||
|
||||
var showLogoutDialog by remember { mutableStateOf(false) } |
||||
var isLastSession by remember { mutableStateOf(false) } |
||||
LaunchedEffect(Unit) { |
||||
isLastSession = encryptionService.isLastDevice().getOrNull() ?: false |
||||
} |
||||
|
||||
fun handleEvents(event: DirectLogoutEvents) { |
||||
when (event) { |
||||
is DirectLogoutEvents.Logout -> { |
||||
if (showLogoutDialog || event.ignoreSdkError) { |
||||
showLogoutDialog = false |
||||
localCoroutineScope.logout(logoutAction, event.ignoreSdkError) |
||||
} else { |
||||
showLogoutDialog = true |
||||
} |
||||
} |
||||
DirectLogoutEvents.CloseDialogs -> { |
||||
logoutAction.value = Async.Uninitialized |
||||
showLogoutDialog = false |
||||
} |
||||
} |
||||
} |
||||
|
||||
return DirectLogoutState( |
||||
canDoDirectSignOut = !isLastSession && |
||||
!backupUploadState.isBackingUp(), |
||||
showConfirmationDialog = showLogoutDialog, |
||||
logoutAction = logoutAction.value, |
||||
eventSink = ::handleEvents |
||||
) |
||||
} |
||||
|
||||
private fun CoroutineScope.logout( |
||||
logoutAction: MutableState<Async<String?>>, |
||||
ignoreSdkError: Boolean, |
||||
) = launch { |
||||
suspend { |
||||
matrixClient.logout(ignoreSdkError) |
||||
}.runCatchingUpdatingState(logoutAction) |
||||
} |
||||
} |
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
/* |
||||
* 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.features.logout.impl.direct |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import com.squareup.anvil.annotations.ContributesBinding |
||||
import io.element.android.features.logout.api.direct.DirectLogoutEvents |
||||
import io.element.android.features.logout.api.direct.DirectLogoutState |
||||
import io.element.android.features.logout.api.direct.DirectLogoutView |
||||
import io.element.android.features.logout.impl.ui.LogoutActionDialog |
||||
import io.element.android.features.logout.impl.ui.LogoutConfirmationDialog |
||||
import io.element.android.libraries.di.SessionScope |
||||
import javax.inject.Inject |
||||
|
||||
@ContributesBinding(SessionScope::class) |
||||
class DefaultDirectLogoutView @Inject constructor() : DirectLogoutView { |
||||
@Composable |
||||
override fun render( |
||||
state: DirectLogoutState, |
||||
onSuccessLogout: (logoutUrlResult: String?) -> Unit, |
||||
) { |
||||
val eventSink = state.eventSink |
||||
// Log out confirmation dialog |
||||
if (state.showConfirmationDialog) { |
||||
LogoutConfirmationDialog( |
||||
onSubmitClicked = { |
||||
eventSink(DirectLogoutEvents.Logout(ignoreSdkError = false)) |
||||
}, |
||||
onDismiss = { |
||||
eventSink(DirectLogoutEvents.CloseDialogs) |
||||
} |
||||
) |
||||
} |
||||
|
||||
LogoutActionDialog( |
||||
state.logoutAction, |
||||
onForceLogoutClicked = { |
||||
eventSink(DirectLogoutEvents.Logout(ignoreSdkError = true)) |
||||
}, |
||||
onDismissError = { |
||||
eventSink(DirectLogoutEvents.CloseDialogs) |
||||
}, |
||||
onSuccessLogout = { |
||||
onSuccessLogout(it) |
||||
}, |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
/* |
||||
* 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.features.logout.impl.tools |
||||
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState |
||||
import io.element.android.libraries.matrix.api.encryption.SteadyStateException |
||||
|
||||
internal fun BackupUploadState.isBackingUp(): Boolean { |
||||
return when (this) { |
||||
BackupUploadState.Waiting, |
||||
is BackupUploadState.Uploading -> true |
||||
is BackupUploadState.SteadyException -> exception is SteadyStateException.Connection |
||||
BackupUploadState.Unknown, |
||||
BackupUploadState.Done, |
||||
BackupUploadState.Error -> false |
||||
} |
||||
} |
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
/* |
||||
* 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.features.logout.impl.ui |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.runtime.LaunchedEffect |
||||
import androidx.compose.ui.res.stringResource |
||||
import io.element.android.features.logout.impl.R |
||||
import io.element.android.libraries.architecture.Async |
||||
import io.element.android.libraries.designsystem.components.ProgressDialog |
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog |
||||
import io.element.android.libraries.ui.strings.CommonStrings |
||||
|
||||
@Composable |
||||
fun LogoutActionDialog( |
||||
state: Async<String?>, |
||||
onForceLogoutClicked: () -> Unit, |
||||
onDismissError: () -> Unit, |
||||
onSuccessLogout: (String?) -> Unit, |
||||
) { |
||||
when (state) { |
||||
is Async.Loading -> |
||||
ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content)) |
||||
is Async.Failure -> |
||||
ConfirmationDialog( |
||||
title = stringResource(id = CommonStrings.dialog_title_error), |
||||
content = stringResource(id = CommonStrings.error_unknown), |
||||
submitText = stringResource(id = CommonStrings.action_signout_anyway), |
||||
onSubmitClicked = onForceLogoutClicked, |
||||
onDismiss = onDismissError, |
||||
) |
||||
Async.Uninitialized -> |
||||
Unit |
||||
is Async.Success -> |
||||
LaunchedEffect(state) { |
||||
onSuccessLogout(state.data) |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
/* |
||||
* 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.features.logout.impl.ui |
||||
|
||||
import androidx.compose.runtime.Composable |
||||
import androidx.compose.ui.res.stringResource |
||||
import io.element.android.features.logout.impl.R |
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog |
||||
import io.element.android.libraries.ui.strings.CommonStrings |
||||
|
||||
@Composable |
||||
fun LogoutConfirmationDialog( |
||||
onSubmitClicked: () -> Unit, |
||||
onDismiss: () -> Unit, |
||||
) { |
||||
ConfirmationDialog( |
||||
title = stringResource(id = CommonStrings.action_signout), |
||||
content = stringResource(id = R.string.screen_signout_confirmation_dialog_content), |
||||
submitText = stringResource(id = CommonStrings.action_signout), |
||||
onSubmitClicked = onSubmitClicked, |
||||
onDismiss = onDismiss, |
||||
) |
||||
} |
Loading…
Reference in new issue