mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 06:55:41 +00:00
Address PR review comments
This commit is contained in:
@@ -38,9 +38,10 @@ import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
|
|||||||
import io.element.android.libraries.matrix.api.sync.SyncService
|
import io.element.android.libraries.matrix.api.sync.SyncService
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||||
import io.element.android.libraries.permissions.api.LocalNetworkPermissionAdvisor
|
|
||||||
import io.element.android.libraries.permissions.api.PermissionsEvent
|
import io.element.android.libraries.permissions.api.PermissionsEvent
|
||||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionAdvisor
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||||
import io.element.android.libraries.push.api.PushService
|
import io.element.android.libraries.push.api.PushService
|
||||||
import io.element.android.libraries.push.api.PusherRegistrationFailure
|
import io.element.android.libraries.push.api.PusherRegistrationFailure
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
package io.element.android.appnav.loggedin
|
package io.element.android.appnav.loggedin
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||||
|
|
||||||
data class LoggedInState(
|
data class LoggedInState(
|
||||||
val showSyncSpinner: Boolean,
|
val showSyncSpinner: Boolean,
|
||||||
@@ -19,9 +20,3 @@ data class LoggedInState(
|
|||||||
val localNetworkPermissionDialog: LocalNetworkPermissionDialog,
|
val localNetworkPermissionDialog: LocalNetworkPermissionDialog,
|
||||||
val eventSink: (LoggedInEvents) -> Unit,
|
val eventSink: (LoggedInEvents) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
enum class LocalNetworkPermissionDialog {
|
|
||||||
None,
|
|
||||||
Rationale,
|
|
||||||
Settings,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ package io.element.android.appnav.loggedin
|
|||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||||
import io.element.android.libraries.push.api.PusherRegistrationFailure
|
import io.element.android.libraries.push.api.PusherRegistrationFailure
|
||||||
|
|
||||||
open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
|
open class LoggedInStateProvider : PreviewParameterProvider<LoggedInState> {
|
||||||
|
|||||||
@@ -19,13 +19,13 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import io.element.android.appnav.R
|
import io.element.android.appnav.R
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialogWithDoNotShowAgain
|
import io.element.android.libraries.designsystem.components.dialogs.ErrorDialogWithDoNotShowAgain
|
||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||||
import io.element.android.libraries.matrix.api.exception.isNetworkError
|
import io.element.android.libraries.matrix.api.exception.isNetworkError
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialogView
|
||||||
import io.element.android.libraries.push.api.PusherRegistrationFailure
|
import io.element.android.libraries.push.api.PusherRegistrationFailure
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@@ -84,38 +84,11 @@ fun LoggedInView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNetworkPermissionDialog(state = state)
|
LocalNetworkPermissionDialogView(
|
||||||
}
|
dialog = state.localNetworkPermissionDialog,
|
||||||
|
onSubmit = { state.eventSink(LoggedInEvents.RequestLocationNetworkPermission) },
|
||||||
@Composable
|
onDismiss = { state.eventSink(LoggedInEvents.DismissLocalNetworkPermissionPrompt) },
|
||||||
private fun LocalNetworkPermissionDialog(state: LoggedInState) {
|
)
|
||||||
when (state.localNetworkPermissionDialog) {
|
|
||||||
LocalNetworkPermissionDialog.None -> Unit
|
|
||||||
LocalNetworkPermissionDialog.Rationale -> ConfirmationDialog(
|
|
||||||
title = stringResource(CommonStrings.screen_local_network_opt_in_title),
|
|
||||||
content = stringResource(CommonStrings.screen_local_network_opt_in_subtitle),
|
|
||||||
submitText = stringResource(CommonStrings.dialog_allow_access),
|
|
||||||
cancelText = stringResource(CommonStrings.action_not_now),
|
|
||||||
onSubmitClick = {
|
|
||||||
state.eventSink(LoggedInEvents.RequestLocationNetworkPermission)
|
|
||||||
},
|
|
||||||
onDismiss = {
|
|
||||||
state.eventSink(LoggedInEvents.DismissLocalNetworkPermissionPrompt)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
LocalNetworkPermissionDialog.Settings -> ConfirmationDialog(
|
|
||||||
title = stringResource(CommonStrings.screen_local_network_opt_in_title),
|
|
||||||
content = stringResource(CommonStrings.screen_local_network_opt_in_subtitle),
|
|
||||||
submitText = stringResource(CommonStrings.action_open_settings),
|
|
||||||
cancelText = stringResource(CommonStrings.action_not_now),
|
|
||||||
onSubmitClick = {
|
|
||||||
state.eventSink(LoggedInEvents.RequestLocationNetworkPermission)
|
|
||||||
},
|
|
||||||
onDismiss = {
|
|
||||||
state.eventSink(LoggedInEvents.DismissLocalNetworkPermissionPrompt)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Throwable.getReason(): String? {
|
private fun Throwable.getReason(): String? {
|
||||||
|
|||||||
+1
-1
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.impl.changeserver
|
package io.element.android.features.login.impl.changeserver
|
||||||
|
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionDialog
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||||
|
|
||||||
data class ChangeServerState(
|
data class ChangeServerState(
|
||||||
val changeServerAction: AsyncData<Unit>,
|
val changeServerAction: AsyncData<Unit>,
|
||||||
|
|||||||
+1
-1
@@ -10,8 +10,8 @@ package io.element.android.features.login.impl.changeserver
|
|||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.features.login.impl.error.ChangeServerError
|
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
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||||
|
|
||||||
open class ChangeServerStateProvider : PreviewParameterProvider<ChangeServerState> {
|
open class ChangeServerStateProvider : PreviewParameterProvider<ChangeServerState> {
|
||||||
override val values: Sequence<ChangeServerState>
|
override val values: Sequence<ChangeServerState>
|
||||||
|
|||||||
+1
-1
@@ -19,7 +19,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||||||
import io.element.android.features.login.impl.R
|
import io.element.android.features.login.impl.R
|
||||||
import io.element.android.features.login.impl.dialogs.SlidingSyncNotSupportedDialog
|
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.error.ChangeServerError
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionDialogView
|
|
||||||
import io.element.android.libraries.androidutils.system.openGooglePlay
|
import io.element.android.libraries.androidutils.system.openGooglePlay
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.designsystem.components.ProgressDialog
|
import io.element.android.libraries.designsystem.components.ProgressDialog
|
||||||
@@ -28,6 +27,7 @@ import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog
|
|||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
|
import io.element.android.libraries.designsystem.theme.LocalBuildMeta
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialogView
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
-44
@@ -1,44 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (c) 2026 Element Creations Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.login.impl.localnetwork
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun LocalNetworkPermissionDialogView(
|
|
||||||
dialog: LocalNetworkPermissionDialog,
|
|
||||||
onSubmit: () -> Unit,
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
when (dialog) {
|
|
||||||
LocalNetworkPermissionDialog.None -> Unit
|
|
||||||
LocalNetworkPermissionDialog.Rationale -> ConfirmationDialog(
|
|
||||||
title = stringResource(CommonStrings.screen_local_network_opt_in_title),
|
|
||||||
content = stringResource(CommonStrings.screen_local_network_opt_in_subtitle),
|
|
||||||
submitText = stringResource(CommonStrings.dialog_allow_access),
|
|
||||||
cancelText = stringResource(CommonStrings.action_not_now),
|
|
||||||
onSubmitClick = onSubmit,
|
|
||||||
onDismiss = onDismiss,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
LocalNetworkPermissionDialog.Settings -> ConfirmationDialog(
|
|
||||||
title = stringResource(CommonStrings.screen_local_network_opt_in_title),
|
|
||||||
content = stringResource(CommonStrings.screen_local_network_opt_in_subtitle),
|
|
||||||
submitText = stringResource(CommonStrings.action_open_settings),
|
|
||||||
cancelText = stringResource(CommonStrings.action_not_now),
|
|
||||||
onSubmitClick = onSubmit,
|
|
||||||
onDismiss = onDismiss,
|
|
||||||
modifier = modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+7
-6
@@ -17,9 +17,10 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.rememberUpdatedState
|
import androidx.compose.runtime.rememberUpdatedState
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.libraries.permissions.api.LocalNetworkPermissionAdvisor
|
|
||||||
import io.element.android.libraries.permissions.api.PermissionsEvent
|
import io.element.android.libraries.permissions.api.PermissionsEvent
|
||||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionAdvisor
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
@@ -39,13 +40,13 @@ class LocalNetworkPermissionGate(
|
|||||||
val permissionsState = permissionsPresenter.present()
|
val permissionsState = permissionsPresenter.present()
|
||||||
var pendingSubmit by remember { mutableStateOf<T?>(null) }
|
var pendingSubmit by remember { mutableStateOf<T?>(null) }
|
||||||
|
|
||||||
val urlOf by rememberUpdatedState(urlOf)
|
val latestUrlOf by rememberUpdatedState(urlOf)
|
||||||
val onProceed by rememberUpdatedState(onProceed)
|
val latestOnProceed by rememberUpdatedState(onProceed)
|
||||||
|
|
||||||
LaunchedEffect(permissionsState.permissionGranted, pendingSubmit) {
|
LaunchedEffect(permissionsState.permissionGranted, pendingSubmit) {
|
||||||
val pending = pendingSubmit
|
val pending = pendingSubmit
|
||||||
if (pending != null && permissionsState.permissionGranted) {
|
if (pending != null && permissionsState.permissionGranted) {
|
||||||
coroutineScope.launch { onProceed(pending) }
|
coroutineScope.launch { latestOnProceed(pending) }
|
||||||
pendingSubmit = null
|
pendingSubmit = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,10 +62,10 @@ class LocalNetworkPermissionGate(
|
|||||||
|
|
||||||
fun submit(value: T) {
|
fun submit(value: T) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
if (advisor.shouldRequestPermissionFor(urlOf(value))) {
|
if (advisor.shouldRequestPermissionFor(latestUrlOf(value))) {
|
||||||
pendingSubmit = value
|
pendingSubmit = value
|
||||||
} else {
|
} else {
|
||||||
onProceed(value)
|
latestOnProceed(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-12
@@ -7,21 +7,11 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.impl.localnetwork
|
package io.element.android.features.login.impl.localnetwork
|
||||||
|
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||||
|
|
||||||
data class LocalNetworkPermissionGateState<T>(
|
data class LocalNetworkPermissionGateState<T>(
|
||||||
val dialog: LocalNetworkPermissionDialog,
|
val dialog: LocalNetworkPermissionDialog,
|
||||||
val submit: (T) -> Unit,
|
val submit: (T) -> Unit,
|
||||||
val requestPermission: () -> Unit,
|
val requestPermission: () -> Unit,
|
||||||
val abort: () -> Unit,
|
val abort: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* Which rationale dialog (if any) should be rendered on top of the caller's UI.
|
|
||||||
*
|
|
||||||
* [Rationale] is shown before the runtime prompt so the user knows why the permission is needed.
|
|
||||||
* [Settings] is shown when the OS says a rationale can no longer be shown (permanently denied).
|
|
||||||
*/
|
|
||||||
enum class LocalNetworkPermissionDialog {
|
|
||||||
None,
|
|
||||||
Rationale,
|
|
||||||
Settings,
|
|
||||||
}
|
|
||||||
|
|||||||
+10
@@ -15,7 +15,11 @@ import androidx.compose.runtime.remember
|
|||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.features.login.impl.error.ChangeServerError
|
import io.element.android.features.login.impl.error.ChangeServerError
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionGate
|
||||||
|
import io.element.android.features.login.impl.screens.chooseaccountprovider.ChooseAccountProviderPresenter
|
||||||
|
import io.element.android.features.login.impl.screens.classic.loginwithclassic.LoginWithClassicPresenter
|
||||||
|
import io.element.android.features.login.impl.screens.confirmaccountprovider.ConfirmAccountProviderPresenter
|
||||||
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
import io.element.android.features.login.impl.screens.createaccount.AccountCreationNotSupported
|
||||||
|
import io.element.android.features.login.impl.screens.onboarding.OnBoardingPresenter
|
||||||
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationRetriever
|
||||||
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 +29,12 @@ import io.element.android.libraries.matrix.api.auth.OAuthPrompt
|
|||||||
import io.element.android.libraries.oauth.api.OAuthAction
|
import io.element.android.libraries.oauth.api.OAuthAction
|
||||||
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Presenter responsible for managing the login flow, including handling OAuth actions and
|
||||||
|
* submitting login requests.
|
||||||
|
* It's a helper to avoid code duplication. It is used by [OnBoardingPresenter], [ConfirmAccountProviderPresenter],
|
||||||
|
* [ChooseAccountProviderPresenter] and [LoginWithClassicPresenter].
|
||||||
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
class LoginModePresenter(
|
class LoginModePresenter(
|
||||||
private val oAuthActionFlow: OAuthActionFlow,
|
private val oAuthActionFlow: OAuthActionFlow,
|
||||||
|
|||||||
+1
-1
@@ -7,9 +7,9 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.impl.login
|
package io.element.android.features.login.impl.login
|
||||||
|
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionDialog
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||||
|
|
||||||
data class LoginModeState(
|
data class LoginModeState(
|
||||||
val loginMode: AsyncData<LoginMode>,
|
val loginMode: AsyncData<LoginMode>,
|
||||||
|
|||||||
+1
-1
@@ -7,8 +7,8 @@
|
|||||||
|
|
||||||
package io.element.android.features.login.impl.login
|
package io.element.android.features.login.impl.login
|
||||||
|
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionDialog
|
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||||
|
|
||||||
fun aLoginModeState(
|
fun aLoginModeState(
|
||||||
loginMode: AsyncData<LoginMode> = AsyncData.Uninitialized,
|
loginMode: AsyncData<LoginMode> = AsyncData.Uninitialized,
|
||||||
|
|||||||
+7
-14
@@ -14,7 +14,7 @@ import io.element.android.libraries.core.coroutine.parallelMap
|
|||||||
import io.element.android.libraries.core.uri.ensureProtocol
|
import io.element.android.libraries.core.uri.ensureProtocol
|
||||||
import io.element.android.libraries.core.uri.isValidUrl
|
import io.element.android.libraries.core.uri.isValidUrl
|
||||||
import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker
|
import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker
|
||||||
import io.element.android.libraries.permissions.api.LocalNetworkPermissionAdvisor
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionAdvisor
|
||||||
import kotlinx.coroutines.currentCoroutineContext
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
@@ -44,21 +44,14 @@ class HomeserverResolver(
|
|||||||
// Skip the compatibility probe if we'd need ACCESS_LOCAL_NETWORK first —
|
// Skip the compatibility probe if we'd need ACCESS_LOCAL_NETWORK first —
|
||||||
// otherwise the probe hangs on the TCP timeout for ~30s. Emit the URL as a
|
// otherwise the probe hangs on the TCP timeout for ~30s. Emit the URL as a
|
||||||
// candidate directly; the actual sign-in flow triggers the permission prompt.
|
// candidate directly; the actual sign-in flow triggers the permission prompt.
|
||||||
if (localNetworkPermissionAdvisor.shouldRequestPermissionFor(url)) {
|
val shouldRequestPermissionOrIsValid = localNetworkPermissionAdvisor.shouldRequestPermissionFor(url) ||
|
||||||
currentList.add(HomeserverData(homeserverUrl = url))
|
homeServerLoginCompatibilityChecker.check(url)
|
||||||
withContext(flowContext) {
|
.onFailure { Timber.w(it, "Failed to check compatibility with homeserver $url") }
|
||||||
emit(currentList.toList())
|
.getOrNull()
|
||||||
}
|
?: return@parallelMap
|
||||||
return@parallelMap
|
|
||||||
}
|
|
||||||
|
|
||||||
val isValid = homeServerLoginCompatibilityChecker.check(url)
|
|
||||||
.onFailure { Timber.w(it, "Failed to check compatibility with homeserver $url") }
|
|
||||||
.getOrNull()
|
|
||||||
?: return@parallelMap
|
|
||||||
|
|
||||||
// Emit the list as soon as possible
|
// Emit the list as soon as possible
|
||||||
if (isValid) {
|
if (shouldRequestPermissionOrIsValid) {
|
||||||
currentList.add(HomeserverData(homeserverUrl = url))
|
currentList.add(HomeserverData(homeserverUrl = url))
|
||||||
withContext(flowContext) {
|
withContext(flowContext) {
|
||||||
emit(currentList.toList())
|
emit(currentList.toList())
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.login.impl.R
|
import io.element.android.features.login.impl.R
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderView
|
import io.element.android.features.login.impl.accountprovider.AccountProviderView
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionDialogView
|
|
||||||
import io.element.android.features.login.impl.login.LoginModeEvent
|
import io.element.android.features.login.impl.login.LoginModeEvent
|
||||||
import io.element.android.features.login.impl.login.LoginModeView
|
import io.element.android.features.login.impl.login.LoginModeView
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
@@ -46,6 +45,7 @@ import io.element.android.libraries.designsystem.theme.components.Button
|
|||||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||||
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialogView
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.login.impl.R
|
import io.element.android.features.login.impl.R
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionDialogView
|
|
||||||
import io.element.android.features.login.impl.login.LoginModeEvent
|
import io.element.android.features.login.impl.login.LoginModeEvent
|
||||||
import io.element.android.features.login.impl.login.LoginModeView
|
import io.element.android.features.login.impl.login.LoginModeView
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
@@ -52,6 +51,7 @@ import io.element.android.libraries.designsystem.theme.components.Button
|
|||||||
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
import io.element.android.libraries.designsystem.theme.components.OutlinedButton
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialogView
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,6 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.login.impl.R
|
import io.element.android.features.login.impl.R
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionDialogView
|
|
||||||
import io.element.android.features.login.impl.login.LoginModeEvent
|
import io.element.android.features.login.impl.login.LoginModeEvent
|
||||||
import io.element.android.features.login.impl.login.LoginModeView
|
import io.element.android.features.login.impl.login.LoginModeView
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
@@ -33,6 +32,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
|||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialogView
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,6 @@ import androidx.compose.ui.unit.dp
|
|||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.login.impl.R
|
import io.element.android.features.login.impl.R
|
||||||
import io.element.android.features.login.impl.localnetwork.LocalNetworkPermissionDialogView
|
|
||||||
import io.element.android.features.login.impl.login.LoginModeEvent
|
import io.element.android.features.login.impl.login.LoginModeEvent
|
||||||
import io.element.android.features.login.impl.login.LoginModeView
|
import io.element.android.features.login.impl.login.LoginModeView
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
@@ -52,6 +51,7 @@ import io.element.android.libraries.designsystem.theme.components.IconSource
|
|||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.designsystem.theme.components.TextButton
|
import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||||
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
import io.element.android.libraries.matrix.api.auth.OAuthDetails
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialogView
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.libraries.testtags.testTag
|
import io.element.android.libraries.testtags.testTag
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,6 @@ 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.AccountProvider
|
||||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
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.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.login.impl.localnetwork.LocalNetworkPermissionGate
|
||||||
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
import io.element.android.features.wellknown.test.FakeWellknownRetriever
|
||||||
import io.element.android.features.wellknown.test.anElementWellKnown
|
import io.element.android.features.wellknown.test.anElementWellKnown
|
||||||
@@ -25,6 +24,7 @@ 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.A_HOMESERVER_URL
|
||||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||||
import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails
|
import io.element.android.libraries.matrix.test.auth.aMatrixHomeServerDetails
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionDialog
|
||||||
import io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor
|
import io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor
|
||||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
|
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
|
||||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
|
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
|
||||||
|
|||||||
+2
-1
@@ -34,6 +34,7 @@ import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationSer
|
|||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||||
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
import io.element.android.libraries.oauth.api.OAuthActionFlow
|
||||||
import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow
|
import io.element.android.libraries.oauth.test.customtab.FakeOAuthActionFlow
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionAdvisor
|
||||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||||
@@ -316,7 +317,7 @@ fun createLoginModePresenter(
|
|||||||
oAuthActionFlow: OAuthActionFlow = FakeOAuthActionFlow(),
|
oAuthActionFlow: OAuthActionFlow = FakeOAuthActionFlow(),
|
||||||
authenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
authenticationService: MatrixAuthenticationService = FakeMatrixAuthenticationService(),
|
||||||
webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(),
|
webClientUrlForAuthenticationRetriever: WebClientUrlForAuthenticationRetriever = FakeWebClientUrlForAuthenticationRetriever(),
|
||||||
localNetworkPermissionAdvisor: io.element.android.libraries.permissions.api.LocalNetworkPermissionAdvisor =
|
localNetworkPermissionAdvisor: LocalNetworkPermissionAdvisor =
|
||||||
io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor(),
|
io.element.android.libraries.permissions.test.FakeLocalNetworkPermissionAdvisor(),
|
||||||
permissionsPresenterFactory: io.element.android.libraries.permissions.api.PermissionsPresenter.Factory =
|
permissionsPresenterFactory: io.element.android.libraries.permissions.api.PermissionsPresenter.Factory =
|
||||||
io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory(),
|
io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory(),
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.permissions.api
|
package io.element.android.libraries.permissions.api.localnetwork
|
||||||
|
|
||||||
interface LocalNetworkPermissionAdvisor {
|
interface LocalNetworkPermissionAdvisor {
|
||||||
/**
|
/**
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.permissions.api.localnetwork
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Which rationale dialog (if any) should be rendered on top of the caller's UI.
|
||||||
|
*
|
||||||
|
* [Rationale] is shown before the runtime prompt so the user knows why the permission is needed.
|
||||||
|
* [Settings] is shown when the OS says a rationale can no longer be shown (permanently denied).
|
||||||
|
*/
|
||||||
|
enum class LocalNetworkPermissionDialog {
|
||||||
|
None,
|
||||||
|
Rationale,
|
||||||
|
Settings,
|
||||||
|
}
|
||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.libraries.permissions.api.localnetwork
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LocalNetworkPermissionDialogView(
|
||||||
|
dialog: LocalNetworkPermissionDialog,
|
||||||
|
onSubmit: () -> Unit,
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
val submitTextRes = when (dialog) {
|
||||||
|
LocalNetworkPermissionDialog.None -> return
|
||||||
|
LocalNetworkPermissionDialog.Rationale -> CommonStrings.dialog_allow_access
|
||||||
|
LocalNetworkPermissionDialog.Settings -> CommonStrings.action_open_settings
|
||||||
|
}
|
||||||
|
ConfirmationDialog(
|
||||||
|
title = stringResource(CommonStrings.screen_local_network_opt_in_title),
|
||||||
|
content = stringResource(CommonStrings.screen_local_network_opt_in_subtitle),
|
||||||
|
submitText = stringResource(submitTextRes),
|
||||||
|
cancelText = stringResource(CommonStrings.action_not_now),
|
||||||
|
onSubmitClick = onSubmit,
|
||||||
|
onDismiss = onDismiss,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
+4
-4
@@ -5,16 +5,16 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.permissions.impl
|
package io.element.android.libraries.permissions.impl.localnetwork
|
||||||
|
|
||||||
import android.Manifest.permission.ACCESS_LOCAL_NETWORK
|
import android.Manifest
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import io.element.android.libraries.androidutils.network.LocalNetworkAddressClassifier
|
import io.element.android.libraries.androidutils.network.LocalNetworkAddressClassifier
|
||||||
import io.element.android.libraries.androidutils.network.LocalNetworkClassification
|
import io.element.android.libraries.androidutils.network.LocalNetworkClassification
|
||||||
import io.element.android.libraries.permissions.api.LocalNetworkPermissionAdvisor
|
|
||||||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||||
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionAdvisor
|
||||||
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
|
||||||
|
|
||||||
@ContributesBinding(AppScope::class)
|
@ContributesBinding(AppScope::class)
|
||||||
@@ -25,7 +25,7 @@ class DefaultLocalNetworkPermissionAdvisor(
|
|||||||
) : LocalNetworkPermissionAdvisor {
|
) : LocalNetworkPermissionAdvisor {
|
||||||
override suspend fun shouldRequestPermissionFor(homeserverUrl: String): Boolean {
|
override suspend fun shouldRequestPermissionFor(homeserverUrl: String): Boolean {
|
||||||
if (!buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.CINNAMON_BUN)) return false
|
if (!buildVersionSdkIntProvider.isAtLeast(Build.VERSION_CODES.CINNAMON_BUN)) return false
|
||||||
if (permissionStateProvider.isPermissionGranted(ACCESS_LOCAL_NETWORK)) return false
|
if (permissionStateProvider.isPermissionGranted(Manifest.permission.ACCESS_LOCAL_NETWORK)) return false
|
||||||
return when (classifier.classify(homeserverUrl)) {
|
return when (classifier.classify(homeserverUrl)) {
|
||||||
LocalNetworkClassification.LocalIp -> true
|
LocalNetworkClassification.LocalIp -> true
|
||||||
LocalNetworkClassification.PublicIp,
|
LocalNetworkClassification.PublicIp,
|
||||||
+1
-1
@@ -5,7 +5,7 @@
|
|||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package io.element.android.libraries.permissions.impl
|
package io.element.android.libraries.permissions.impl.localnetwork
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.androidutils.network.LocalNetworkAddressClassifier
|
import io.element.android.libraries.androidutils.network.LocalNetworkAddressClassifier
|
||||||
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
package io.element.android.libraries.permissions.test
|
package io.element.android.libraries.permissions.test
|
||||||
|
|
||||||
import io.element.android.libraries.permissions.api.LocalNetworkPermissionAdvisor
|
import io.element.android.libraries.permissions.api.localnetwork.LocalNetworkPermissionAdvisor
|
||||||
|
|
||||||
class FakeLocalNetworkPermissionAdvisor(
|
class FakeLocalNetworkPermissionAdvisor(
|
||||||
private val shouldPrompt: Boolean = false,
|
private val shouldPrompt: Boolean = false,
|
||||||
|
|||||||
Reference in New Issue
Block a user