Prompt for local network permission when changing server
This commit is contained in:
+2
@@ -13,4 +13,6 @@ import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
sealed interface ChangeServerEvents {
|
||||
data class ChangeServer(val accountProvider: AccountProvider) : ChangeServerEvents
|
||||
data object ClearError : ChangeServerEvents
|
||||
data object RequestLocalNetworkPermission : ChangeServerEvents
|
||||
data object DismissLocalNetworkPermission : ChangeServerEvents
|
||||
}
|
||||
|
||||
+13
-8
@@ -12,50 +12,55 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.login.impl.accesscontrol.DefaultAccountProviderAccessControl
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
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.localnetwork.LocalNetworkPermissionGate
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Inject
|
||||
class ChangeServerPresenter(
|
||||
private val authenticationService: MatrixAuthenticationService,
|
||||
private val accountProviderDataSource: AccountProviderDataSource,
|
||||
private val defaultAccountProviderAccessControl: DefaultAccountProviderAccessControl,
|
||||
private val localNetworkPermissionGate: LocalNetworkPermissionGate,
|
||||
) : Presenter<ChangeServerState> {
|
||||
@Composable
|
||||
override fun present(): ChangeServerState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
val changeServerAction: MutableState<AsyncData<Unit>> = remember {
|
||||
mutableStateOf(AsyncData.Uninitialized)
|
||||
}
|
||||
|
||||
val gateState = localNetworkPermissionGate.present<AccountProvider>(
|
||||
urlOf = { provider -> provider.url },
|
||||
onProceed = { provider -> changeServer(provider, changeServerAction) },
|
||||
)
|
||||
|
||||
fun handleEvent(event: ChangeServerEvents) {
|
||||
when (event) {
|
||||
is ChangeServerEvents.ChangeServer -> localCoroutineScope.changeServer(event.accountProvider, changeServerAction)
|
||||
ChangeServerEvents.ClearError -> changeServerAction.value = AsyncData.Uninitialized
|
||||
is ChangeServerEvents.ChangeServer -> gateState.submit(event.accountProvider)
|
||||
ChangeServerEvents.DismissLocalNetworkPermission -> gateState.abort()
|
||||
ChangeServerEvents.RequestLocalNetworkPermission -> gateState.requestPermission()
|
||||
}
|
||||
}
|
||||
|
||||
return ChangeServerState(
|
||||
changeServerAction = changeServerAction.value,
|
||||
localNetworkPermissionDialog = gateState.dialog,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.changeServer(
|
||||
private suspend fun changeServer(
|
||||
data: AccountProvider,
|
||||
changeServerAction: MutableState<AsyncData<Unit>>,
|
||||
) = launch {
|
||||
) {
|
||||
suspend {
|
||||
defaultAccountProviderAccessControl.assertIsAllowedToConnectToAccountProvider(
|
||||
title = data.title,
|
||||
|
||||
+2
@@ -8,9 +8,11 @@
|
||||
|
||||
package io.element.android.features.login.impl.changeserver
|
||||
|
||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionDialog
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
||||
data class ChangeServerState(
|
||||
val changeServerAction: AsyncData<Unit>,
|
||||
val localNetworkPermissionDialog: LocalNetworkPermissionDialog,
|
||||
val eventSink: (ChangeServerEvents) -> Unit
|
||||
)
|
||||
|
||||
+4
@@ -10,6 +10,7 @@ package io.element.android.features.login.impl.changeserver
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.login.impl.error.ChangeServerError
|
||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionDialog
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
||||
open class ChangeServerStateProvider : PreviewParameterProvider<ChangeServerState> {
|
||||
@@ -39,12 +40,15 @@ open class ChangeServerStateProvider : PreviewParameterProvider<ChangeServerStat
|
||||
ChangeServerError.UnsupportedServer
|
||||
)
|
||||
),
|
||||
aChangeServerState(localNetworkPermissionDialog = LocalNetworkPermissionDialog.Rationale),
|
||||
)
|
||||
}
|
||||
|
||||
fun aChangeServerState(
|
||||
changeServerAction: AsyncData<Unit> = AsyncData.Uninitialized,
|
||||
localNetworkPermissionDialog: LocalNetworkPermissionDialog = LocalNetworkPermissionDialog.None,
|
||||
) = ChangeServerState(
|
||||
changeServerAction = changeServerAction,
|
||||
localNetworkPermissionDialog = localNetworkPermissionDialog,
|
||||
eventSink = {}
|
||||
)
|
||||
|
||||
+10
@@ -19,6 +19,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.features.login.impl.R
|
||||
import io.element.android.features.login.impl.dialogs.SlidingSyncNotSupportedDialog
|
||||
import io.element.android.features.login.impl.error.ChangeServerError
|
||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionDialogView
|
||||
import io.element.android.libraries.androidutils.system.openGooglePlay
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||
@@ -121,6 +122,15 @@ fun ChangeServerView(
|
||||
}
|
||||
AsyncData.Uninitialized -> Unit
|
||||
}
|
||||
LocalNetworkPermissionDialogView(
|
||||
dialog = state.localNetworkPermissionDialog,
|
||||
onSubmit = {
|
||||
eventSink.invoke(ChangeServerEvents.RequestLocalNetworkPermission)
|
||||
},
|
||||
onDismiss = {
|
||||
eventSink.invoke(ChangeServerEvents.DismissLocalNetworkPermission)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
|
||||
+80
@@ -15,6 +15,8 @@ import io.element.android.features.login.impl.accesscontrol.DefaultAccountProvid
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
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.localnetwork.LocalNetworkPermissionDialog
|
||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
||||
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
||||
import io.element.android.features.wellknown.test.anElementWellKnown
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
@@ -23,6 +25,9 @@ import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails
|
||||
import io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor
|
||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
|
||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
|
||||
import io.element.android.libraries.wellknown.api.ElementWellKnown
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetriever
|
||||
import io.element.android.libraries.wellknown.api.WellknownRetrieverResult
|
||||
@@ -43,6 +48,7 @@ class ChangeServerPresenterTest {
|
||||
createPresenter().test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
||||
assertThat(initialState.localNetworkPermissionDialog).isEqualTo(LocalNetworkPermissionDialog.None)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,11 +189,81 @@ class ChangeServerPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - advisor advises prompt, dialog shown before setHomeserver runs`() = runTest {
|
||||
val authenticationService = FakeMatrixAuthenticationService(
|
||||
setHomeserverResult = { Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true)) },
|
||||
)
|
||||
createPresenter(
|
||||
authenticationService = authenticationService,
|
||||
enterpriseService = FakeEnterpriseService(isAllowedToConnectToHomeserverResult = { true }),
|
||||
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true),
|
||||
permissionsPresenter = FakePermissionsPresenter(),
|
||||
).test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.localNetworkPermissionDialog).isEqualTo(LocalNetworkPermissionDialog.None)
|
||||
initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(url = A_HOMESERVER_URL)))
|
||||
val promptState = expectMostRecentItem()
|
||||
// Dialog is shown, permission has not been requested yet, setHomeserver has not been called.
|
||||
assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None)
|
||||
assertThat(promptState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - dismissing the dialog aborts the submit`() = runTest {
|
||||
createPresenter(
|
||||
authenticationService = FakeMatrixAuthenticationService(
|
||||
setHomeserverResult = { Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true)) },
|
||||
),
|
||||
enterpriseService = FakeEnterpriseService(isAllowedToConnectToHomeserverResult = { true }),
|
||||
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true),
|
||||
permissionsPresenter = FakePermissionsPresenter(),
|
||||
).test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(url = A_HOMESERVER_URL)))
|
||||
val promptState = expectMostRecentItem()
|
||||
assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None)
|
||||
promptState.eventSink.invoke(ChangeServerEvents.DismissLocalNetworkPermission)
|
||||
val dismissedState = expectMostRecentItem()
|
||||
assertThat(dismissedState.localNetworkPermissionDialog).isEqualTo(LocalNetworkPermissionDialog.None)
|
||||
assertThat(dismissedState.changeServerAction).isEqualTo(AsyncData.Uninitialized)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - advisor advises prompt, permission granted resumes setHomeserver`() = runTest {
|
||||
val authenticationService = FakeMatrixAuthenticationService(
|
||||
setHomeserverResult = { Result.success(aMatrixHomeServerDetails(supportsOAuthLogin = true)) },
|
||||
)
|
||||
val permissionsPresenter = FakePermissionsPresenter()
|
||||
createPresenter(
|
||||
authenticationService = authenticationService,
|
||||
enterpriseService = FakeEnterpriseService(isAllowedToConnectToHomeserverResult = { true }),
|
||||
localNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(shouldPrompt = true),
|
||||
permissionsPresenter = permissionsPresenter,
|
||||
).test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(ChangeServerEvents.ChangeServer(AccountProvider(url = A_HOMESERVER_URL)))
|
||||
val promptState = expectMostRecentItem()
|
||||
assertThat(promptState.localNetworkPermissionDialog).isNotEqualTo(LocalNetworkPermissionDialog.None)
|
||||
permissionsPresenter.setPermissionGranted()
|
||||
// Await recompositions until the deferred setHomeserver completes.
|
||||
var finalState = awaitItem()
|
||||
while (finalState.changeServerAction !is AsyncData.Success) {
|
||||
finalState = awaitItem()
|
||||
}
|
||||
assertThat(finalState.changeServerAction).isEqualTo(AsyncData.Success(Unit))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPresenter(
|
||||
authenticationService: FakeMatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||
accountProviderDataSource: AccountProviderDataSource = AccountProviderDataSource(FakeEnterpriseService()),
|
||||
enterpriseService: EnterpriseService = FakeEnterpriseService(),
|
||||
wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(),
|
||||
localNetworkPermissionAdvisor: FakeLocalNetworkPermissionAdvisor = FakeLocalNetworkPermissionAdvisor(),
|
||||
permissionsPresenter: FakePermissionsPresenter = FakePermissionsPresenter(),
|
||||
) = ChangeServerPresenter(
|
||||
authenticationService = authenticationService,
|
||||
accountProviderDataSource = accountProviderDataSource,
|
||||
@@ -195,5 +271,9 @@ class ChangeServerPresenterTest {
|
||||
enterpriseService = enterpriseService,
|
||||
wellknownRetriever = wellknownRetriever,
|
||||
),
|
||||
localNetworkPermissionGate = LocalNetworkPermissionGate(
|
||||
advisor = localNetworkPermissionAdvisor,
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user