Merge remote-tracking branch 'origin/develop' into feature/fre/improve_poll_event_timeline_rendering
This commit is contained in:
commit
e6490b3a89
71 changed files with 674 additions and 189 deletions
|
|
@ -17,6 +17,7 @@
|
|||
package io.element.android.features.login.impl.screens.confirmaccountprovider
|
||||
|
||||
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
|
||||
|
|
@ -26,8 +27,11 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.login.api.oidc.OidcAction
|
||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.features.login.impl.error.ChangeServerError
|
||||
import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
|
|
@ -40,7 +44,9 @@ import java.net.URL
|
|||
class ConfirmAccountProviderPresenter @AssistedInject constructor(
|
||||
@Assisted private val params: Params,
|
||||
private val accountProviderDataSource: AccountProviderDataSource,
|
||||
private val authenticationService: MatrixAuthenticationService
|
||||
private val authenticationService: MatrixAuthenticationService,
|
||||
private val defaultOidcActionFlow: DefaultOidcActionFlow,
|
||||
private val defaultLoginUserStory: DefaultLoginUserStory,
|
||||
) : Presenter<ConfirmAccountProviderState> {
|
||||
|
||||
data class Params(
|
||||
|
|
@ -61,6 +67,14 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
|
|||
mutableStateOf(Async.Uninitialized)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
launch {
|
||||
defaultOidcActionFlow.collect {
|
||||
onOidcAction(it, loginFlowAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun handleEvents(event: ConfirmAccountProviderEvents) {
|
||||
when (event) {
|
||||
ConfirmAccountProviderEvents.Continue -> {
|
||||
|
|
@ -97,4 +111,33 @@ class ConfirmAccountProviderPresenter @AssistedInject constructor(
|
|||
}.getOrThrow()
|
||||
}.runCatchingUpdatingState(loginFlowAction, errorTransform = ChangeServerError::from)
|
||||
}
|
||||
|
||||
private suspend fun onOidcAction(
|
||||
oidcAction: OidcAction?,
|
||||
loginFlowAction: MutableState<Async<LoginFlow>>,
|
||||
) {
|
||||
oidcAction ?: return
|
||||
loginFlowAction.value = Async.Loading()
|
||||
when (oidcAction) {
|
||||
OidcAction.GoBack -> {
|
||||
authenticationService.cancelOidcLogin()
|
||||
.onSuccess {
|
||||
loginFlowAction.value = Async.Uninitialized
|
||||
}
|
||||
.onFailure { failure ->
|
||||
loginFlowAction.value = Async.Failure(failure)
|
||||
}
|
||||
}
|
||||
is OidcAction.Success -> {
|
||||
authenticationService.loginWithOidc(oidcAction.url)
|
||||
.onSuccess { _ ->
|
||||
defaultLoginUserStory.setLoginFlowIsDone(true)
|
||||
}
|
||||
.onFailure { failure ->
|
||||
loginFlowAction.value = Async.Failure(failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
defaultOidcActionFlow.reset()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider
|
|||
object LoginConstants {
|
||||
const val MATRIX_ORG_URL = "matrix.org"
|
||||
|
||||
const val DEFAULT_HOMESERVER_URL = "matrix.org" // TODO Oidc "synapse-oidc.lab.element.dev"
|
||||
const val DEFAULT_HOMESERVER_URL = "matrix.org"
|
||||
const val SLIDING_SYNC_READ_MORE_URL = "https://github.com/matrix-org/sliding-sync/blob/main/docs/Landing.md"
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,24 +20,25 @@ import app.cash.molecule.RecompositionMode
|
|||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.login.api.oidc.OidcAction
|
||||
import io.element.android.features.login.impl.DefaultLoginUserStory
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.features.login.impl.oidc.customtab.DefaultOidcActionFlow
|
||||
import io.element.android.features.login.impl.util.defaultAccountProvider
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_OIDC
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService
|
||||
import io.element.android.tests.testutils.waitForPredicate
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class ConfirmAccountProviderPresenterTest {
|
||||
@Test
|
||||
fun `present - initial test`() = runTest {
|
||||
val presenter = ConfirmAccountProviderPresenter(
|
||||
ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
|
||||
AccountProviderDataSource(),
|
||||
FakeAuthenticationService(),
|
||||
)
|
||||
val presenter = createConfirmAccountProviderPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -51,13 +52,11 @@ class ConfirmAccountProviderPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - continue password login`() = runTest {
|
||||
val authServer = FakeAuthenticationService()
|
||||
val presenter = ConfirmAccountProviderPresenter(
|
||||
ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
|
||||
AccountProviderDataSource(),
|
||||
authServer,
|
||||
val authenticationService = FakeAuthenticationService()
|
||||
val presenter = createConfirmAccountProviderPresenter(
|
||||
matrixAuthenticationService = authenticationService,
|
||||
)
|
||||
authServer.givenHomeserver(A_HOMESERVER)
|
||||
authenticationService.givenHomeserver(A_HOMESERVER)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -75,13 +74,11 @@ class ConfirmAccountProviderPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - continue oidc`() = runTest {
|
||||
val authServer = FakeAuthenticationService()
|
||||
val presenter = ConfirmAccountProviderPresenter(
|
||||
ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
|
||||
AccountProviderDataSource(),
|
||||
authServer,
|
||||
val authenticationService = FakeAuthenticationService()
|
||||
val presenter = createConfirmAccountProviderPresenter(
|
||||
matrixAuthenticationService = authenticationService,
|
||||
)
|
||||
authServer.givenHomeserver(A_HOMESERVER_OIDC)
|
||||
authenticationService.givenHomeserver(A_HOMESERVER_OIDC)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -97,19 +94,135 @@ class ConfirmAccountProviderPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - oidc - cancel with failure`() = runTest {
|
||||
val authenticationService = FakeAuthenticationService()
|
||||
val defaultOidcActionFlow = DefaultOidcActionFlow()
|
||||
val presenter = createConfirmAccountProviderPresenter(
|
||||
matrixAuthenticationService = authenticationService,
|
||||
defaultOidcActionFlow = defaultOidcActionFlow,
|
||||
)
|
||||
authenticationService.givenHomeserver(A_HOMESERVER_OIDC)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.submitEnabled).isTrue()
|
||||
assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java)
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.submitEnabled).isFalse()
|
||||
assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java)
|
||||
assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
|
||||
authenticationService.givenOidcCancelError(A_THROWABLE)
|
||||
defaultOidcActionFlow.post(OidcAction.GoBack)
|
||||
val cancelFailureState = awaitItem()
|
||||
assertThat(cancelFailureState.loginFlow).isInstanceOf(Async.Failure::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - oidc - cancel with success`() = runTest {
|
||||
val authenticationService = FakeAuthenticationService()
|
||||
val defaultOidcActionFlow = DefaultOidcActionFlow()
|
||||
val presenter = createConfirmAccountProviderPresenter(
|
||||
matrixAuthenticationService = authenticationService,
|
||||
defaultOidcActionFlow = defaultOidcActionFlow,
|
||||
)
|
||||
authenticationService.givenHomeserver(A_HOMESERVER_OIDC)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.submitEnabled).isTrue()
|
||||
assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java)
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.submitEnabled).isFalse()
|
||||
assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java)
|
||||
assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
|
||||
defaultOidcActionFlow.post(OidcAction.GoBack)
|
||||
val cancelFinalState = awaitItem()
|
||||
assertThat(cancelFinalState.loginFlow).isInstanceOf(Async.Uninitialized::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - oidc - success with failure`() = runTest {
|
||||
val authenticationService = FakeAuthenticationService()
|
||||
val defaultOidcActionFlow = DefaultOidcActionFlow()
|
||||
val presenter = createConfirmAccountProviderPresenter(
|
||||
matrixAuthenticationService = authenticationService,
|
||||
defaultOidcActionFlow = defaultOidcActionFlow,
|
||||
)
|
||||
authenticationService.givenHomeserver(A_HOMESERVER_OIDC)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.submitEnabled).isTrue()
|
||||
assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java)
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.submitEnabled).isFalse()
|
||||
assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java)
|
||||
assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
|
||||
authenticationService.givenLoginError(A_THROWABLE)
|
||||
defaultOidcActionFlow.post(OidcAction.Success("aUrl"))
|
||||
val cancelLoadingState = awaitItem()
|
||||
assertThat(cancelLoadingState.loginFlow).isInstanceOf(Async.Loading::class.java)
|
||||
val cancelFailureState = awaitItem()
|
||||
assertThat(cancelFailureState.loginFlow).isInstanceOf(Async.Failure::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - oidc - success with success`() = runTest {
|
||||
val authenticationService = FakeAuthenticationService()
|
||||
val defaultOidcActionFlow = DefaultOidcActionFlow()
|
||||
val defaultLoginUserStory = DefaultLoginUserStory().apply {
|
||||
setLoginFlowIsDone(false)
|
||||
}
|
||||
val presenter = createConfirmAccountProviderPresenter(
|
||||
matrixAuthenticationService = authenticationService,
|
||||
defaultOidcActionFlow = defaultOidcActionFlow,
|
||||
defaultLoginUserStory = defaultLoginUserStory,
|
||||
)
|
||||
authenticationService.givenHomeserver(A_HOMESERVER_OIDC)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.submitEnabled).isTrue()
|
||||
assertThat(loadingState.loginFlow).isInstanceOf(Async.Loading::class.java)
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.submitEnabled).isFalse()
|
||||
assertThat(successState.loginFlow).isInstanceOf(Async.Success::class.java)
|
||||
assertThat(successState.loginFlow.dataOrNull()).isInstanceOf(LoginFlow.OidcFlow::class.java)
|
||||
assertThat(defaultLoginUserStory.loginFlowIsDone.value).isFalse()
|
||||
defaultOidcActionFlow.post(OidcAction.Success("aUrl"))
|
||||
val successSuccessState = awaitItem()
|
||||
assertThat(successSuccessState.loginFlow).isInstanceOf(Async.Loading::class.java)
|
||||
waitForPredicate { defaultLoginUserStory.loginFlowIsDone.value }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - submit fails`() = runTest {
|
||||
val authServer = FakeAuthenticationService()
|
||||
val presenter = ConfirmAccountProviderPresenter(
|
||||
ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
|
||||
AccountProviderDataSource(),
|
||||
authServer,
|
||||
val authenticationService = FakeAuthenticationService()
|
||||
val presenter = createConfirmAccountProviderPresenter(
|
||||
matrixAuthenticationService = authenticationService,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
authServer.givenChangeServerError(Throwable())
|
||||
authenticationService.givenChangeServerError(Throwable())
|
||||
initialState.eventSink.invoke(ConfirmAccountProviderEvents.Continue)
|
||||
skipItems(1) // Loading
|
||||
val failureState = awaitItem()
|
||||
|
|
@ -121,10 +234,8 @@ class ConfirmAccountProviderPresenterTest {
|
|||
@Test
|
||||
fun `present - clear error`() = runTest {
|
||||
val authenticationService = FakeAuthenticationService()
|
||||
val presenter = ConfirmAccountProviderPresenter(
|
||||
ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
|
||||
AccountProviderDataSource(),
|
||||
authenticationService,
|
||||
val presenter = createConfirmAccountProviderPresenter(
|
||||
matrixAuthenticationService = authenticationService,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -147,4 +258,18 @@ class ConfirmAccountProviderPresenterTest {
|
|||
assertThat(clearedState.loginFlow).isEqualTo(Async.Uninitialized)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createConfirmAccountProviderPresenter(
|
||||
params: ConfirmAccountProviderPresenter.Params = ConfirmAccountProviderPresenter.Params(isAccountCreation = false),
|
||||
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(),
|
||||
matrixAuthenticationService: MatrixAuthenticationService = FakeAuthenticationService(),
|
||||
defaultOidcActionFlow: DefaultOidcActionFlow = DefaultOidcActionFlow(),
|
||||
defaultLoginUserStory: DefaultLoginUserStory = DefaultLoginUserStory(),
|
||||
) = ConfirmAccountProviderPresenter(
|
||||
params = params,
|
||||
accountProviderDataSource = accountProviderDataSource,
|
||||
authenticationService = matrixAuthenticationService,
|
||||
defaultOidcActionFlow = defaultOidcActionFlow,
|
||||
defaultLoginUserStory = defaultLoginUserStory,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,12 +34,12 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
|||
@Composable
|
||||
fun LogoutPreferenceView(
|
||||
state: LogoutPreferenceState,
|
||||
onSuccessLogout: () -> Unit = {}
|
||||
onSuccessLogout: (String?) -> Unit = {}
|
||||
) {
|
||||
val eventSink = state.eventSink
|
||||
if (state.logoutAction is Async.Success) {
|
||||
LaunchedEffect(state.logoutAction) {
|
||||
onSuccessLogout()
|
||||
onSuccessLogout(state.logoutAction.data)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,6 @@ package io.element.android.features.logout.api
|
|||
import io.element.android.libraries.architecture.Async
|
||||
|
||||
data class LogoutPreferenceState(
|
||||
val logoutAction: Async<Unit>,
|
||||
val logoutAction: Async<String?>,
|
||||
val eventSink: (LogoutPreferenceEvents) -> Unit,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class DefaultLogoutPreferencePresenter @Inject constructor(private val matrixCli
|
|||
@Composable
|
||||
override fun present(): LogoutPreferenceState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val logoutAction: MutableState<Async<Unit>> = remember {
|
||||
val logoutAction: MutableState<Async<String?>> = remember {
|
||||
mutableStateOf(Async.Uninitialized)
|
||||
}
|
||||
|
||||
|
|
@ -56,7 +56,7 @@ class DefaultLogoutPreferencePresenter @Inject constructor(private val matrixCli
|
|||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.logout(logoutAction: MutableState<Async<Unit>>) = launch {
|
||||
private fun CoroutineScope.logout(logoutAction: MutableState<Async<String?>>) = launch {
|
||||
suspend {
|
||||
matrixClient.logout()
|
||||
}.runCatchingUpdatingState(logoutAction)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ fun TimelineItemTextView(
|
|||
}
|
||||
ClickableLinkText(
|
||||
text = textWithPadding,
|
||||
linkAnnotationTag = "URL",
|
||||
onClick = onTextClicked,
|
||||
onLongClick = onTextLongClicked,
|
||||
interactionSource = interactionSource
|
||||
|
|
|
|||
|
|
@ -582,7 +582,6 @@ private fun HtmlText(
|
|||
val inlineContentMap = persistentMapOf<String, InlineTextContent>()
|
||||
ClickableLinkText(
|
||||
annotatedString = text,
|
||||
linkAnnotationTag = "URL",
|
||||
style = style,
|
||||
modifier = modifier,
|
||||
inlineContent = inlineContentMap,
|
||||
|
|
|
|||
|
|
@ -26,11 +26,13 @@
|
|||
<string name="screen_room_notification_settings_default_setting_footnote">"You can change it in your %1$s."</string>
|
||||
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"global settings"</string>
|
||||
<string name="screen_room_notification_settings_default_setting_title">"Default setting"</string>
|
||||
<string name="screen_room_notification_settings_edit_remove_setting">"Remove custom setting"</string>
|
||||
<string name="screen_room_notification_settings_error_loading_settings">"An error occurred while loading notification settings."</string>
|
||||
<string name="screen_room_notification_settings_error_restoring_default">"Failed restoring the default mode, please try again."</string>
|
||||
<string name="screen_room_notification_settings_error_setting_mode">"Failed setting the mode, please try again."</string>
|
||||
<string name="screen_room_notification_settings_mode_all_messages">"All messages"</string>
|
||||
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Mentions and Keywords only"</string>
|
||||
<string name="screen_room_notification_settings_room_custom_settings_title">"In this room, notify me for"</string>
|
||||
<string name="screen_room_reactions_show_less">"Show less"</string>
|
||||
<string name="screen_room_reactions_show_more">"Show more"</string>
|
||||
<string name="screen_room_retry_send_menu_send_again_action">"Send again"</string>
|
||||
|
|
|
|||
|
|
@ -16,8 +16,10 @@
|
|||
|
||||
package io.element.android.features.preferences.impl.root
|
||||
|
||||
import android.app.Activity
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
|
|
@ -25,7 +27,9 @@ import com.bumble.appyx.core.plugin.plugins
|
|||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import timber.log.Timber
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
class PreferencesRootNode @AssistedInject constructor(
|
||||
|
|
@ -62,9 +66,16 @@ class PreferencesRootNode @AssistedInject constructor(
|
|||
plugins<Callback>().forEach { it.onOpenAbout() }
|
||||
}
|
||||
|
||||
private fun onManageAccountClicked(activity: Activity, accountManagementUrl: String?) {
|
||||
accountManagementUrl?.let {
|
||||
activity.openUrlInChromeCustomTab(null, false, it)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
val activity = LocalContext.current as Activity
|
||||
PreferencesRootView(
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
|
|
@ -73,7 +84,16 @@ class PreferencesRootNode @AssistedInject constructor(
|
|||
onOpenAnalytics = this::onOpenAnalytics,
|
||||
onOpenAbout = this::onOpenAbout,
|
||||
onVerifyClicked = this::onVerifyClicked,
|
||||
onOpenDeveloperSettings = this::onOpenDeveloperSettings
|
||||
onOpenDeveloperSettings = this::onOpenDeveloperSettings,
|
||||
onSuccessLogout = { onSuccessLogout(activity, it) },
|
||||
onManageAccountClicked = { onManageAccountClicked(activity, state.accountManagementUrl) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSuccessLogout(activity: Activity, url: String?) {
|
||||
Timber.d("Success logout with result url: $url")
|
||||
url?.let {
|
||||
activity.openUrlInChromeCustomTab(null, false, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -34,7 +33,6 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
|||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.api.user.getCurrentUser
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -62,10 +60,15 @@ class PreferencesRootPresenter @Inject constructor(
|
|||
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
|
||||
val hasAnalyticsProviders = remember { analyticsService.getAvailableAnalyticsProviders().isNotEmpty() }
|
||||
|
||||
// Session verification status (unknown, not verified, verified)
|
||||
val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState()
|
||||
val sessionIsNotVerified by remember {
|
||||
derivedStateOf { sessionVerifiedStatus == SessionVerifiedStatus.NotVerified }
|
||||
// We should display the 'complete verification' option if the current session can be verified
|
||||
val showCompleteVerification by sessionVerificationService.canVerifySessionFlow.collectAsState(false)
|
||||
|
||||
val accountManagementUrl: MutableState<String?> = remember {
|
||||
mutableStateOf(null)
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
initAccountManagementUrl(accountManagementUrl)
|
||||
}
|
||||
|
||||
val logoutState = logoutPresenter.present()
|
||||
|
|
@ -74,7 +77,8 @@ class PreferencesRootPresenter @Inject constructor(
|
|||
logoutState = logoutState,
|
||||
myUser = matrixUser.value,
|
||||
version = versionFormatter.get(),
|
||||
showCompleteVerification = sessionIsNotVerified,
|
||||
showCompleteVerification = showCompleteVerification,
|
||||
accountManagementUrl = accountManagementUrl.value,
|
||||
showAnalyticsSettings = hasAnalyticsProviders,
|
||||
showDeveloperSettings = showDeveloperSettings,
|
||||
snackbarMessage = snackbarMessage,
|
||||
|
|
@ -84,4 +88,8 @@ class PreferencesRootPresenter @Inject constructor(
|
|||
private fun CoroutineScope.initialLoad(matrixUser: MutableState<MatrixUser?>) = launch {
|
||||
matrixUser.value = matrixClient.getCurrentUser()
|
||||
}
|
||||
|
||||
private fun CoroutineScope.initAccountManagementUrl(accountManagementUrl: MutableState<String?>) = launch {
|
||||
accountManagementUrl.value = matrixClient.getAccountManagementUrl().getOrNull()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ data class PreferencesRootState(
|
|||
val myUser: MatrixUser?,
|
||||
val version: String,
|
||||
val showCompleteVerification: Boolean,
|
||||
val accountManagementUrl: String?,
|
||||
val showAnalyticsSettings: Boolean,
|
||||
val showDeveloperSettings: Boolean,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ fun aPreferencesRootState() = PreferencesRootState(
|
|||
myUser = null,
|
||||
version = "Version 1.1 (1)",
|
||||
showCompleteVerification = true,
|
||||
accountManagementUrl = "aUrl",
|
||||
showAnalyticsSettings = true,
|
||||
showDeveloperSettings = true,
|
||||
snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete),
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import androidx.compose.material.icons.outlined.BugReport
|
|||
import androidx.compose.material.icons.outlined.DeveloperMode
|
||||
import androidx.compose.material.icons.outlined.Help
|
||||
import androidx.compose.material.icons.outlined.InsertChart
|
||||
import androidx.compose.material.icons.outlined.ManageAccounts
|
||||
import androidx.compose.material.icons.outlined.VerifiedUser
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -51,10 +52,12 @@ fun PreferencesRootView(
|
|||
state: PreferencesRootState,
|
||||
onBackPressed: () -> Unit,
|
||||
onVerifyClicked: () -> Unit,
|
||||
onManageAccountClicked: () -> Unit,
|
||||
onOpenAnalytics: () -> Unit,
|
||||
onOpenRageShake: () -> Unit,
|
||||
onOpenAbout: () -> Unit,
|
||||
onOpenDeveloperSettings: () -> Unit,
|
||||
onSuccessLogout: (String?) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
|
|
@ -75,6 +78,13 @@ fun PreferencesRootView(
|
|||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
if (state.accountManagementUrl != null) {
|
||||
PreferenceText(
|
||||
title = stringResource(id = CommonStrings.screen_settings_oidc_account),
|
||||
icon = Icons.Outlined.ManageAccounts,
|
||||
onClick = onManageAccountClicked,
|
||||
)
|
||||
}
|
||||
if (state.showAnalyticsSettings) {
|
||||
PreferenceText(
|
||||
title = stringResource(id = CommonStrings.common_analytics),
|
||||
|
|
@ -98,6 +108,7 @@ fun PreferencesRootView(
|
|||
HorizontalDivider()
|
||||
LogoutPreferenceView(
|
||||
state = state.logoutState,
|
||||
onSuccessLogout = onSuccessLogout,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
|
|
@ -140,5 +151,7 @@ private fun ContentToPreview(matrixUser: MatrixUser) {
|
|||
onOpenDeveloperSettings = {},
|
||||
onOpenAbout = {},
|
||||
onVerifyClicked = {},
|
||||
onSuccessLogout = {},
|
||||
onManageAccountClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ class PreferencesRootPresenterTest {
|
|||
)
|
||||
assertThat(loadedState.showDeveloperSettings).isEqualTo(true)
|
||||
assertThat(loadedState.showAnalyticsSettings).isEqualTo(false)
|
||||
assertThat(loadedState.accountManagementUrl).isNull()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,6 @@ import io.element.android.libraries.matrix.api.MatrixClient
|
|||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.api.user.getCurrentUser
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
|
@ -73,11 +72,11 @@ class RoomListPresenter @Inject constructor(
|
|||
}
|
||||
|
||||
// Session verification status (unknown, not verified, verified)
|
||||
val sessionVerifiedStatus by sessionVerificationService.sessionVerifiedStatus.collectAsState()
|
||||
val canVerifySession by sessionVerificationService.canVerifySessionFlow.collectAsState(initial = false)
|
||||
var verificationPromptDismissed by rememberSaveable { mutableStateOf(false) }
|
||||
// We combine both values to only display the prompt if the session is not verified and it wasn't dismissed
|
||||
val displayVerificationPrompt by remember {
|
||||
derivedStateOf { sessionVerifiedStatus == SessionVerifiedStatus.NotVerified && !verificationPromptDismissed }
|
||||
derivedStateOf { canVerifySession && !verificationPromptDismissed }
|
||||
}
|
||||
|
||||
var displaySearchResults by rememberSaveable { mutableStateOf(false) }
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ class RoomListPresenterTests {
|
|||
fun `present - handle DismissRequestVerificationPrompt`() = runTest {
|
||||
val roomListService = FakeRoomListService()
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService
|
||||
roomListService = roomListService,
|
||||
)
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue