mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-02 12:07:28 +00:00
Merge branch 'develop' into feature/bma/otherAccountUnreadCount
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+5
@@ -46,6 +46,7 @@ import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.mediaviewer.api.FileViewerEntryPoint
|
||||
import io.element.android.libraries.troubleshoot.api.NotificationTroubleShootEntryPoint
|
||||
@@ -141,6 +142,10 @@ class PreferencesFlowNode(
|
||||
callback.navigateToAddAccount()
|
||||
}
|
||||
|
||||
override fun navigateToSession(sessionId: SessionId) {
|
||||
callback.navigateToSession(sessionId)
|
||||
}
|
||||
|
||||
override fun navigateToAnalyticsSettings() {
|
||||
backstack.push(NavTarget.AnalyticsSettings)
|
||||
}
|
||||
|
||||
+13
-7
@@ -26,6 +26,7 @@ import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingStateNoSuccess
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
@@ -57,6 +58,7 @@ import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@@ -76,6 +78,7 @@ class NotificationSettingsPresenter(
|
||||
@SessionCoroutineScope
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) : Presenter<NotificationSettingsState> {
|
||||
// Serializes the pick → copy → persist → recreate pipeline per slot. The copier already locks
|
||||
// its `<slot>.tmp` file, but the persist+recreate window outside the copier is unprotected;
|
||||
@@ -111,16 +114,19 @@ class NotificationSettingsPresenter(
|
||||
}
|
||||
|
||||
// List of PushProvider -> Distributor
|
||||
val distributors = remember {
|
||||
pushService.getAvailablePushProviders()
|
||||
.flatMap { pushProvider ->
|
||||
pushProvider.getDistributors().map { distributor ->
|
||||
pushProvider to distributor
|
||||
// Getting the distributors can query the PackageManager and the Play Services, so do it off the main thread
|
||||
val distributors by produceState(emptyList<Pair<PushProvider, Distributor>>()) {
|
||||
value = withContext(dispatchers.io) {
|
||||
pushService.getAvailablePushProviders()
|
||||
.flatMap { pushProvider ->
|
||||
pushProvider.getDistributors().map { distributor ->
|
||||
pushProvider to distributor
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// List of Distributors
|
||||
val availableDistributors = remember {
|
||||
val availableDistributors = remember(distributors) {
|
||||
distributors.map { it.second }.toImmutableList()
|
||||
}
|
||||
|
||||
|
||||
-3
@@ -8,11 +8,8 @@
|
||||
|
||||
package io.element.android.features.preferences.impl.root
|
||||
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
sealed interface PreferencesRootEvent {
|
||||
data object OnVersionInfoClick : PreferencesRootEvent
|
||||
data class SwitchToSession(val sessionId: SessionId) : PreferencesRootEvent
|
||||
data object ToggleOtherAccountsExpanded : PreferencesRootEvent
|
||||
data class SetTheme(val theme: ThemeOption) : PreferencesRootEvent
|
||||
}
|
||||
|
||||
+3
@@ -24,6 +24,7 @@ import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTa
|
||||
import io.element.android.libraries.architecture.callback
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
@ContributesNode(SessionScope::class)
|
||||
@AssistedInject
|
||||
@@ -35,6 +36,7 @@ class PreferencesRootNode(
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : PreferencesAccountCallback, Plugin {
|
||||
fun navigateToAddAccount()
|
||||
fun navigateToSession(sessionId: SessionId)
|
||||
fun navigateToAnalyticsSettings()
|
||||
fun navigateToAbout()
|
||||
fun navigateToDeveloperSettings()
|
||||
@@ -72,6 +74,7 @@ class PreferencesRootNode(
|
||||
modifier = modifier,
|
||||
onBackClick = this::navigateUp,
|
||||
onAddAccountClick = callback::navigateToAddAccount,
|
||||
onSwitchToSession = callback::navigateToSession,
|
||||
onOpenAnalytics = callback::navigateToAnalyticsSettings,
|
||||
onOpenAbout = callback::navigateToAbout,
|
||||
onOpenDeveloperSettings = callback::navigateToDeveloperSettings,
|
||||
|
||||
-3
@@ -142,9 +142,6 @@ class PreferencesRootPresenter(
|
||||
is PreferencesRootEvent.OnVersionInfoClick -> {
|
||||
showDeveloperSettingsProvider.unlockDeveloperSettings(coroutineScope)
|
||||
}
|
||||
is PreferencesRootEvent.SwitchToSession -> coroutineScope.launch {
|
||||
sessionStore.setLatestSession(event.sessionId.value)
|
||||
}
|
||||
PreferencesRootEvent.ToggleOtherAccountsExpanded -> coroutineScope.launch {
|
||||
appPreferencesStore.setOtherAccountsExpanded(!isOtherAccountsSectionExpanded)
|
||||
}
|
||||
|
||||
+6
-1
@@ -66,6 +66,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
|
||||
import io.element.android.libraries.emoji.api.picker.EmojiPickerRenderer
|
||||
import io.element.android.libraries.emoji.api.picker.NoOpEmojiPickerRenderer
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
|
||||
import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
@@ -80,6 +81,7 @@ fun PreferencesRootView(
|
||||
emojiPickerRenderer: EmojiPickerRenderer,
|
||||
onBackClick: () -> Unit,
|
||||
onAddAccountClick: () -> Unit,
|
||||
onSwitchToSession: (SessionId) -> Unit,
|
||||
onOpenAnalytics: () -> Unit,
|
||||
onOpenLockScreenSettings: () -> Unit,
|
||||
onOpenAbout: () -> Unit,
|
||||
@@ -125,6 +127,7 @@ fun PreferencesRootView(
|
||||
MultiAccountSection(
|
||||
state = state,
|
||||
onAddAccountClick = onAddAccountClick,
|
||||
onSwitchToSession = onSwitchToSession,
|
||||
)
|
||||
}
|
||||
HorizontalDivider(
|
||||
@@ -209,6 +212,7 @@ private fun ColumnScope.UserStatusSection(
|
||||
private fun ColumnScope.MultiAccountSection(
|
||||
state: PreferencesRootState,
|
||||
onAddAccountClick: () -> Unit,
|
||||
onSwitchToSession: (SessionId) -> Unit,
|
||||
) {
|
||||
HorizontalDivider(
|
||||
thickness = 8.dp,
|
||||
@@ -276,7 +280,7 @@ private fun ColumnScope.MultiAccountSection(
|
||||
MatrixUserRow(
|
||||
modifier = Modifier
|
||||
.clickable {
|
||||
state.eventSink(PreferencesRootEvent.SwitchToSession(otherAccount.user.userId))
|
||||
onSwitchToSession(otherAccount.user.userId)
|
||||
}
|
||||
.padding(top = 2.dp, bottom = 2.dp, end = 8.dp),
|
||||
matrixUser = otherAccount.user,
|
||||
@@ -433,6 +437,7 @@ private fun ContentToPreview(state: PreferencesRootState) {
|
||||
emojiPickerRenderer = NoOpEmojiPickerRenderer,
|
||||
onBackClick = {},
|
||||
onAddAccountClick = {},
|
||||
onSwitchToSession = {},
|
||||
onOpenAnalytics = {},
|
||||
onOpenDeveloperSettings = {},
|
||||
onOpenLocationSettings = {},
|
||||
|
||||
+2
@@ -19,6 +19,7 @@ import io.element.android.features.logout.test.FakeLogoutEntryPoint
|
||||
import io.element.android.features.preferences.api.PreferencesEntryPoint
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.mediaviewer.test.FakeFileViewerEntryPoint
|
||||
import io.element.android.libraries.troubleshoot.test.FakeNotificationTroubleShootEntryPoint
|
||||
import io.element.android.libraries.troubleshoot.test.FakePushHistoryEntryPoint
|
||||
@@ -52,6 +53,7 @@ class DefaultPreferencesEntryPointTest {
|
||||
}
|
||||
val callback = object : PreferencesEntryPoint.Callback {
|
||||
override fun navigateToAddAccount() = lambdaError()
|
||||
override fun navigateToSession(sessionId: SessionId) = lambdaError()
|
||||
override fun navigateToLinkNewDevice() = lambdaError()
|
||||
override fun navigateToBugReport() = lambdaError()
|
||||
override fun navigateToSecureBackup() = lambdaError()
|
||||
|
||||
+2
@@ -40,6 +40,7 @@ import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
@@ -1176,6 +1177,7 @@ class NotificationSettingsPresenterTest {
|
||||
stringProvider = FakeStringProvider(),
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
featureFlagService = featureFlagService,
|
||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
-20
@@ -44,8 +44,6 @@ import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -184,24 +182,6 @@ class PreferencesRootPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - switch session invoke method on the session store`() = runTest {
|
||||
val setLatestSessionResult = lambdaRecorder<String, Unit> { }
|
||||
val sessionStore = InMemorySessionStore(
|
||||
initialList = listOf(
|
||||
aSessionData(sessionId = A_SESSION_ID.value),
|
||||
aSessionData(sessionId = A_SESSION_ID_2.value),
|
||||
),
|
||||
setLatestSessionResult = setLatestSessionResult,
|
||||
)
|
||||
createPreferencesRootPresenter(sessionStore = sessionStore).test {
|
||||
val loadedState = awaitFirstItem()
|
||||
loadedState.eventSink(PreferencesRootEvent.SwitchToSession(A_SESSION_ID_2))
|
||||
setLatestSessionResult.assertions().isCalledOnce()
|
||||
.with(value(A_SESSION_ID_2.value))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - labs can be shown if any feature flag is in labs and not finished`() = runTest {
|
||||
createPreferencesRootPresenter(
|
||||
|
||||
+23
-18
@@ -20,6 +20,7 @@ import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import io.element.android.features.preferences.impl.R
|
||||
import io.element.android.libraries.emoji.api.picker.NoOpEmojiPickerRenderer
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
@@ -69,25 +70,27 @@ class PreferencesRootViewTest : RobolectricTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on other session sends a SwitchToSession`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>()
|
||||
setView(
|
||||
aPreferencesRootState(
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = true,
|
||||
otherSessions = listOf(
|
||||
anOtherAccount(
|
||||
user = aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
),
|
||||
)
|
||||
fun `clicking on other session invokes the expected callback`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||
ensureCalledOnceWithParam(A_USER_ID_2) { callback ->
|
||||
setView(
|
||||
aPreferencesRootState(
|
||||
isMultiAccountEnabled = true,
|
||||
isOtherAccountsSectionExpanded = true,
|
||||
otherSessions = listOf(
|
||||
anOtherAccount(
|
||||
user = aMatrixUser(
|
||||
id = A_USER_ID_2.value,
|
||||
displayName = "Bob",
|
||||
),
|
||||
)
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
onNodeWithText("Bob").performClick()
|
||||
eventsRecorder.assertSingle(PreferencesRootEvent.SwitchToSession(A_USER_ID_2))
|
||||
onSwitchToSession = callback,
|
||||
)
|
||||
onNodeWithText("Bob").performClick()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -425,6 +428,7 @@ private fun AndroidComposeUiTest<ComponentActivity>.setView(
|
||||
state: PreferencesRootState,
|
||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||
onAddAccountClick: () -> Unit = EnsureNeverCalled(),
|
||||
onSwitchToSession: (SessionId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onOpenAnalytics: () -> Unit = EnsureNeverCalled(),
|
||||
onOpenLockScreenSettings: () -> Unit = EnsureNeverCalled(),
|
||||
onOpenAbout: () -> Unit = EnsureNeverCalled(),
|
||||
@@ -448,6 +452,7 @@ private fun AndroidComposeUiTest<ComponentActivity>.setView(
|
||||
emojiPickerRenderer = NoOpEmojiPickerRenderer,
|
||||
onBackClick = onBackClick,
|
||||
onAddAccountClick = onAddAccountClick,
|
||||
onSwitchToSession = onSwitchToSession,
|
||||
onOpenAnalytics = onOpenAnalytics,
|
||||
onOpenLockScreenSettings = onOpenLockScreenSettings,
|
||||
onOpenAbout = onOpenAbout,
|
||||
|
||||
Reference in New Issue
Block a user