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/fga/timeline-multi-forward
This commit is contained in:
@@ -2,17 +2,25 @@ appId: ${MAESTRO_APP_ID}
|
||||
---
|
||||
- tapOn:
|
||||
id: "home_screen-settings"
|
||||
- tapOn:
|
||||
id: "settings-user_profile"
|
||||
- assertVisible: "Your account"
|
||||
- tapOn: "Remove this device"
|
||||
- takeScreenshot: build/maestro/900-SignOutScreen
|
||||
- back
|
||||
- tapOn: "Remove this device"
|
||||
# Ensure pressing back cancels
|
||||
- back
|
||||
- tapOn: "Remove this device"
|
||||
# Ensure cancel cancels
|
||||
- tapOn: "Cancel"
|
||||
- tapOn: "Remove this device"
|
||||
- tapOn:
|
||||
text: "Remove this device"
|
||||
index: 1
|
||||
# Cancel the dialog cancels the logout
|
||||
- tapOn:
|
||||
id: "dialog-negative"
|
||||
- tapOn: "Remove this device"
|
||||
- tapOn:
|
||||
text: "Remove this device"
|
||||
index: 1
|
||||
- tapOn:
|
||||
id: "dialog-positive"
|
||||
- runFlow: ../assertions/assertInitDisplayed.yaml
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
Changes in Element X v26.09.4
|
||||
=============================
|
||||
|
||||
## Hotifx version:
|
||||
## Hotfix version:
|
||||
|
||||
Fixes an issue with the room list not displaying all activity from unread messages.
|
||||
|
||||
|
||||
@@ -6,12 +6,9 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.appnav.room.joined
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
|
||||
@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -68,7 +67,6 @@ fun LoadingRoomNodeView(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun LoadingRoomTopBar(
|
||||
onBackClick: () -> Unit,
|
||||
|
||||
+1
-1
Submodule enterprise updated: 6b422f4c8e...bcffe42d5c
-1
@@ -121,7 +121,6 @@ private fun FullscreenAnnouncementHeader(
|
||||
subTitle = announcement.subtitle(),
|
||||
iconStyle = BigIcon.Style.Default(
|
||||
vectorIcon = announcement.icon(),
|
||||
usePrimaryTint = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
-2
@@ -11,7 +11,6 @@ package io.element.android.features.createroom.impl.addpeople
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -68,7 +67,6 @@ fun AddPeopleView(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun AddPeopleTopBar(
|
||||
onSkipClick: () -> Unit,
|
||||
|
||||
-2
@@ -24,7 +24,6 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -179,7 +178,6 @@ fun ConfigureRoomView(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ConfigureRoomToolbar(
|
||||
isSpace: Boolean,
|
||||
|
||||
-2
@@ -23,7 +23,6 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -69,7 +68,6 @@ import io.element.android.libraries.testtags.testTag
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AccountDeactivationView(
|
||||
state: AccountDeactivationState,
|
||||
|
||||
+6
-3
@@ -15,6 +15,7 @@ import io.element.android.compound.colors.SemanticColorsLightDark
|
||||
import io.element.android.features.enterprise.api.BugReportUrl
|
||||
import io.element.android.features.enterprise.api.EnterpriseService
|
||||
import io.element.android.libraries.androidutils.json.JsonProvider
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.mapCatchingExceptions
|
||||
import io.element.android.libraries.core.uri.ensureProtocol
|
||||
import io.element.android.libraries.matrix.api.ClientUrlContentFetcher
|
||||
@@ -23,6 +24,7 @@ import io.element.android.libraries.matrix.api.accountprovider.AccountProvider
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import timber.log.Timber
|
||||
@@ -31,15 +33,16 @@ import timber.log.Timber
|
||||
class DefaultEnterpriseService(
|
||||
private val temporaryMatrixClientFactory: TemporaryMatrixClientFactory,
|
||||
private val jsonProvider: JsonProvider,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) : EnterpriseService {
|
||||
override suspend fun isEnterpriseUser(sessionId: SessionId) = false
|
||||
override suspend fun tweakMasUrl(url: String, urlContentFetcher: ClientUrlContentFetcher) = url
|
||||
override fun accountProviderAllowList(): List<AccountProvider> = emptyList()
|
||||
override fun canConnectToAnyAccountProvider(): Boolean = true
|
||||
override suspend fun isAllowedToConnectToAccountProvider(accountProvider: AccountProvider) = true
|
||||
override suspend fun isElementProEnforced(serverName: String): Boolean {
|
||||
val temporaryMatrixClient = temporaryMatrixClientFactory.create(serverName).getOrElse { return false }
|
||||
return temporaryMatrixClient.use { client ->
|
||||
override suspend fun isElementProEnforced(serverName: String): Boolean = withContext(dispatchers.io) {
|
||||
val temporaryMatrixClient = temporaryMatrixClientFactory.create(serverName).getOrElse { return@withContext false }
|
||||
temporaryMatrixClient.use { client ->
|
||||
val baseUrl = serverName.ensureProtocol().removeSuffix("/")
|
||||
// We'll always perform a network request here since we're not interested in any cached value.
|
||||
client.getUrl("$baseUrl/.well-known/element/element.json")
|
||||
|
||||
+6
-3
@@ -21,19 +21,21 @@ import io.element.android.libraries.matrix.test.FakeTemporaryMatrixClientFactory
|
||||
import io.element.android.libraries.matrix.test.accountprovider.anAccountProviderGeneric
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultEnterpriseServiceTest {
|
||||
@Test
|
||||
fun accountProviderAllowList() {
|
||||
fun accountProviderAllowList() = runTest {
|
||||
val defaultEnterpriseService = createDefaultEnterpriseService()
|
||||
assertThat(defaultEnterpriseService.accountProviderAllowList()).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `canConnectToAnyAccountProvider is always true`() {
|
||||
fun `canConnectToAnyAccountProvider is always true`() = runTest {
|
||||
val defaultEnterpriseService = createDefaultEnterpriseService()
|
||||
assertThat(defaultEnterpriseService.canConnectToAnyAccountProvider()).isTrue()
|
||||
}
|
||||
@@ -132,11 +134,12 @@ class DefaultEnterpriseServiceTest {
|
||||
closeLambda.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
private fun createDefaultEnterpriseService(
|
||||
private fun TestScope.createDefaultEnterpriseService(
|
||||
client: FakeTemporaryMatrixClient = FakeTemporaryMatrixClient(),
|
||||
jsonProvider: JsonProvider = { Json }
|
||||
) = DefaultEnterpriseService(
|
||||
temporaryMatrixClientFactory = FakeTemporaryMatrixClientFactory(createResult = { Result.success(client) }),
|
||||
jsonProvider = jsonProvider,
|
||||
dispatchers = testCoroutineDispatchers(),
|
||||
)
|
||||
}
|
||||
|
||||
-2
@@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -42,7 +41,6 @@ import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ChooseSelfVerificationModeView(
|
||||
state: ChooseSelfVerificationModeState,
|
||||
|
||||
@@ -53,7 +53,6 @@ dependencies {
|
||||
implementation(projects.features.announcement.api)
|
||||
implementation(projects.features.invite.api)
|
||||
implementation(projects.features.networkmonitor.api)
|
||||
implementation(projects.features.logout.api)
|
||||
implementation(projects.features.leaveroom.api)
|
||||
implementation(projects.features.rageshake.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
@@ -67,7 +66,6 @@ dependencies {
|
||||
testCommonDependencies(libs, true)
|
||||
testImplementation(projects.features.announcement.test)
|
||||
testImplementation(projects.features.invite.test)
|
||||
testImplementation(projects.features.logout.test)
|
||||
testImplementation(projects.features.networkmonitor.test)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.featureflag.test)
|
||||
|
||||
-3
@@ -40,7 +40,6 @@ import io.element.android.features.invite.api.InviteData
|
||||
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteView
|
||||
import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomRenderer
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutView
|
||||
import io.element.android.features.reportroom.api.ReportRoomEntryPoint
|
||||
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesEntryPoint
|
||||
import io.element.android.features.rolesandpermissions.api.ChangeRoomMemberRolesListType
|
||||
@@ -83,7 +82,6 @@ class HomeFlowNode(
|
||||
private val inviteFriendsUseCase: InviteFriendsUseCase,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val acceptDeclineInviteView: AcceptDeclineInviteView,
|
||||
private val directLogoutView: DirectLogoutView,
|
||||
private val reportRoomEntryPoint: ReportRoomEntryPoint,
|
||||
private val declineInviteAndBlockUserEntryPoint: DeclineInviteAndBlockEntryPoint,
|
||||
private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint,
|
||||
@@ -248,7 +246,6 @@ class HomeFlowNode(
|
||||
)
|
||||
}
|
||||
)
|
||||
directLogoutView.Render(state.directLogoutState)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-4
@@ -21,7 +21,6 @@ import androidx.compose.runtime.setValue
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
@@ -42,7 +41,6 @@ class HomePresenter(
|
||||
private val indicatorService: IndicatorService,
|
||||
private val roomListPresenter: Presenter<RoomListState>,
|
||||
private val homeSpacesPresenter: Presenter<HomeSpacesState>,
|
||||
private val logoutPresenter: Presenter<DirectLogoutState>,
|
||||
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
||||
private val sessionStore: SessionStore,
|
||||
) : Presenter<HomeState> {
|
||||
@@ -75,7 +73,6 @@ class HomePresenter(
|
||||
}
|
||||
// Avatar indicator
|
||||
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
|
||||
val directLogoutState = logoutPresenter.present()
|
||||
|
||||
fun handleEvent(event: HomeEvent) {
|
||||
when (event) {
|
||||
@@ -98,7 +95,6 @@ class HomePresenter(
|
||||
homeSpacesState = homeSpacesState,
|
||||
snackbarMessage = snackbarMessage,
|
||||
canReportBug = canReportBug,
|
||||
directLogoutState = directLogoutState,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ package io.element.android.features.home.impl
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
|
||||
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
@@ -29,7 +28,6 @@ data class HomeState(
|
||||
val homeSpacesState: HomeSpacesState,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
val canReportBug: Boolean,
|
||||
val directLogoutState: DirectLogoutState,
|
||||
val eventSink: (HomeEvent) -> Unit,
|
||||
) {
|
||||
val isBackHandlerEnabled = currentHomeNavigationBarItem != HomeNavigationBarItem.Chats || roomListState.spaceFiltersState is SpaceFiltersState.Selected
|
||||
|
||||
-4
@@ -16,8 +16,6 @@ import io.element.android.features.home.impl.roomlist.aRoomsContentState
|
||||
import io.element.android.features.home.impl.roomlist.generateRoomListRoomSummaryList
|
||||
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
||||
import io.element.android.features.home.impl.spaces.aHomeSpacesState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
@@ -63,7 +61,6 @@ internal fun aHomeState(
|
||||
roomListState: RoomListState = aRoomListState(),
|
||||
homeSpacesState: HomeSpacesState = aHomeSpacesState(),
|
||||
canReportBug: Boolean = true,
|
||||
directLogoutState: DirectLogoutState = aDirectLogoutState(),
|
||||
eventSink: (HomeEvent) -> Unit = {}
|
||||
) = HomeState(
|
||||
currentUserAndNeighbors = currentUserAndNeighbors.toImmutableList(),
|
||||
@@ -71,7 +68,6 @@ internal fun aHomeState(
|
||||
hasNetworkConnection = hasNetworkConnection,
|
||||
snackbarMessage = snackbarMessage,
|
||||
canReportBug = canReportBug,
|
||||
directLogoutState = directLogoutState,
|
||||
currentHomeNavigationBarItem = currentHomeNavigationBarItem,
|
||||
roomListState = roomListState,
|
||||
homeSpacesState = homeSpacesState,
|
||||
|
||||
+45
-41
@@ -22,8 +22,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.plus
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.FabPosition
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.rememberTopAppBarState
|
||||
@@ -150,7 +148,6 @@ fun HomeView(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun HomeScaffold(
|
||||
state: HomeState,
|
||||
@@ -187,7 +184,7 @@ private fun HomeScaffold(
|
||||
val spacesLazyListState = rememberLazyListState()
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||
modifier = modifier,
|
||||
topBar = {
|
||||
HomeTopBar(
|
||||
selectedNavigationItem = state.currentHomeNavigationBarItem,
|
||||
@@ -268,42 +265,50 @@ private fun HomeScaffold(
|
||||
val contentPadding = PaddingValues(
|
||||
bottom = 96.dp,
|
||||
)
|
||||
when (state.currentHomeNavigationBarItem) {
|
||||
HomeNavigationBarItem.Chats -> {
|
||||
RoomListContentView(
|
||||
contentState = roomListState.contentState,
|
||||
filtersState = roomListState.filtersState,
|
||||
spaceFiltersState = roomListState.spaceFiltersState,
|
||||
lazyListState = roomsLazyListState,
|
||||
hideInvitesAvatars = roomListState.hideInvitesAvatars,
|
||||
eventSink = roomListState.eventSink,
|
||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = ::onRoomClick,
|
||||
onCreateRoomClick = onStartChatClick,
|
||||
contentPadding = lazyColumnContentPadding + contentPadding,
|
||||
modifier = Modifier
|
||||
.padding(outerPadding)
|
||||
.consumeWindowInsets(outerPadding)
|
||||
)
|
||||
SpaceFiltersView(roomListState.spaceFiltersState)
|
||||
}
|
||||
HomeNavigationBarItem.Spaces -> {
|
||||
HomeSpacesView(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(outerPadding)
|
||||
.consumeWindowInsets(outerPadding),
|
||||
contentPadding = lazyColumnContentPadding + contentPadding,
|
||||
state = state.homeSpacesState,
|
||||
lazyListState = spacesLazyListState,
|
||||
onSpaceClick = { spaceId ->
|
||||
onRoomClick(spaceId)
|
||||
},
|
||||
onCreateSpaceClick = onCreateSpaceClick,
|
||||
// TODO use actual callbacks for this
|
||||
onExploreClick = {},
|
||||
)
|
||||
// The nested scroll connection is set on the content and not on the Scaffold, else the top bar would
|
||||
// consume the scroll events of its own content.
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||
) {
|
||||
when (state.currentHomeNavigationBarItem) {
|
||||
HomeNavigationBarItem.Chats -> {
|
||||
RoomListContentView(
|
||||
contentState = roomListState.contentState,
|
||||
filtersState = roomListState.filtersState,
|
||||
spaceFiltersState = roomListState.spaceFiltersState,
|
||||
lazyListState = roomsLazyListState,
|
||||
hideInvitesAvatars = roomListState.hideInvitesAvatars,
|
||||
eventSink = roomListState.eventSink,
|
||||
onSetUpRecoveryClick = onSetUpRecoveryClick,
|
||||
onConfirmRecoveryKeyClick = onConfirmRecoveryKeyClick,
|
||||
onRoomClick = ::onRoomClick,
|
||||
onCreateRoomClick = onStartChatClick,
|
||||
contentPadding = lazyColumnContentPadding + contentPadding,
|
||||
modifier = Modifier
|
||||
.padding(outerPadding)
|
||||
.consumeWindowInsets(outerPadding)
|
||||
)
|
||||
SpaceFiltersView(roomListState.spaceFiltersState)
|
||||
}
|
||||
HomeNavigationBarItem.Spaces -> {
|
||||
HomeSpacesView(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(outerPadding)
|
||||
.consumeWindowInsets(outerPadding),
|
||||
contentPadding = lazyColumnContentPadding + contentPadding,
|
||||
state = state.homeSpacesState,
|
||||
lazyListState = spacesLazyListState,
|
||||
onSpaceClick = { spaceId ->
|
||||
onRoomClick(spaceId)
|
||||
},
|
||||
onCreateSpaceClick = onCreateSpaceClick,
|
||||
// TODO use actual callbacks for this
|
||||
onExploreClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -325,7 +330,6 @@ private fun HomeFloatingActionButton(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
private fun HomeBottomBar(
|
||||
currentHomeNavigationBarItem: HomeNavigationBarItem,
|
||||
|
||||
-8
@@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.pager.VerticalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||
@@ -100,7 +99,6 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomeTopBar(
|
||||
selectedNavigationItem: HomeNavigationBarItem,
|
||||
@@ -449,7 +447,6 @@ private fun StatusEmojiBadge(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun HomeTopBarPreview() = ElementPreview {
|
||||
@@ -470,7 +467,6 @@ internal fun HomeTopBarPreview() = ElementPreview {
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun HomeTopBarSpaceFiltersSelectedPreview() = ElementPreview {
|
||||
@@ -491,7 +487,6 @@ internal fun HomeTopBarSpaceFiltersSelectedPreview() = ElementPreview {
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun HomeTopBarSpacesPreview() = ElementPreview {
|
||||
@@ -512,7 +507,6 @@ internal fun HomeTopBarSpacesPreview() = ElementPreview {
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
|
||||
@@ -533,7 +527,6 @@ internal fun HomeTopBarWithIndicatorPreview() = ElementPreview {
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun HomeTopBarWithStatusPreview() = ElementPreview {
|
||||
@@ -560,7 +553,6 @@ internal fun HomeTopBarWithStatusPreview() = ElementPreview {
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun HomeTopBarMultiAccountPreview() = ElementPreview {
|
||||
|
||||
-2
@@ -26,7 +26,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.input.TextFieldLineLimits
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -88,7 +87,6 @@ internal fun RoomListSearchView(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun RoomListSearchContent(
|
||||
state: RoomListSearchState,
|
||||
|
||||
-1
@@ -36,7 +36,6 @@ class DefaultHomeEntryPointTest : RobolectricTest() {
|
||||
inviteFriendsUseCase = { lambdaError() },
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
acceptDeclineInviteView = { _, _, _, _ -> lambdaError() },
|
||||
directLogoutView = { _ -> lambdaError() },
|
||||
reportRoomEntryPoint = { _, _, _ -> lambdaError() },
|
||||
declineInviteAndBlockUserEntryPoint = FakeDeclineInviteAndBlockEntryPoint(),
|
||||
changeRoomMemberRolesEntryPoint = { _, _, _, _ -> lambdaError() },
|
||||
|
||||
-2
@@ -12,7 +12,6 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.home.impl.roomlist.aRoomListState
|
||||
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
||||
import io.element.android.features.home.impl.spaces.aHomeSpacesState
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
@@ -162,7 +161,6 @@ internal fun createHomePresenter(
|
||||
indicatorService = indicatorService,
|
||||
roomListPresenter = { aRoomListState() },
|
||||
homeSpacesPresenter = homeSpacesPresenter,
|
||||
logoutPresenter = { aDirectLogoutState() },
|
||||
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
||||
sessionStore = sessionStore,
|
||||
)
|
||||
|
||||
+14
-1
@@ -217,6 +217,10 @@ class RoomListPresenterTest {
|
||||
val presenter = createRoomListPresenter(client = client)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Skip intermediate event for loading the value of the `UnreadIndicatorCount` feature flag
|
||||
skipItems(1)
|
||||
|
||||
val summary = createRoomListRoomSummary()
|
||||
initialState.eventSink(RoomListEvent.ShowContextMenu(summary))
|
||||
|
||||
@@ -260,6 +264,10 @@ class RoomListPresenterTest {
|
||||
val presenter = createRoomListPresenter(client = client)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Skip intermediate event for loading the value of the `UnreadIndicatorCount` feature flag
|
||||
skipItems(1)
|
||||
|
||||
val summary = createRoomListRoomSummary()
|
||||
initialState.eventSink(RoomListEvent.ShowContextMenu(summary))
|
||||
|
||||
@@ -309,6 +317,10 @@ class RoomListPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Skip intermediate event for loading the value of the `UnreadIndicatorCount` feature flag
|
||||
skipItems(1)
|
||||
|
||||
eventRecorder.assertEmpty()
|
||||
initialState.eventSink(RoomListEvent.ToggleSearchResults)
|
||||
eventRecorder.assertSingle(
|
||||
@@ -398,6 +410,7 @@ class RoomListPresenterTest {
|
||||
client = matrixClient,
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1) // Skip initial state
|
||||
assertThat(awaitItem().contentState).isInstanceOf(RoomListContentState.Empty::class.java)
|
||||
}
|
||||
}
|
||||
@@ -616,7 +629,7 @@ class RoomListPresenterTest {
|
||||
)
|
||||
presenter.test {
|
||||
assertThat(announcementService.announcementsToShowFlow().first()).isEmpty()
|
||||
skipItems(1)
|
||||
skipItems(2) // Skip initial state and intermediate event for loading the value of the `UnreadIndicatorCount` feature flag
|
||||
val state = awaitItem()
|
||||
assertThat(state.contentAsRooms().showNewNotificationSoundBanner).isFalse()
|
||||
announcementService.emitAnnouncementsToShow(listOf(Announcement.NewNotificationSound))
|
||||
|
||||
-2
@@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
@@ -41,7 +40,6 @@ import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DeclineAndBlockView(
|
||||
state: DeclineAndBlockState,
|
||||
|
||||
-1
@@ -171,7 +171,6 @@ private fun InvitePeopleContentView(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun InvitePeopleSearchBar(
|
||||
queryState: TextFieldState,
|
||||
|
||||
-2
@@ -24,7 +24,6 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -593,7 +592,6 @@ private fun DefaultLoadedContent(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun JoinRoomTopBar(
|
||||
contentState: ContentState,
|
||||
|
||||
-2
@@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
@@ -479,7 +478,6 @@ private fun KnockRequestsEmptyList(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun KnockRequestsListTopBar(onBackClick: () -> Unit) {
|
||||
TopAppBar(
|
||||
|
||||
-2
@@ -12,7 +12,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.plus
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -28,7 +27,6 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.designsystem.utils.lazyColumnContentPadding
|
||||
import io.element.android.libraries.designsystem.utils.scaffoldScrollableContentInsets
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DependenciesDetailsView(
|
||||
licenseItem: DependencyLicenseItem,
|
||||
|
||||
-2
@@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.plus
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -39,7 +38,6 @@ import io.element.android.libraries.designsystem.utils.lazyColumnContentPadding
|
||||
import io.element.android.libraries.designsystem.utils.scaffoldScrollableContentInsets
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun DependencyLicensesListView(
|
||||
state: DependencyLicensesListState,
|
||||
|
||||
-3
@@ -5,8 +5,6 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.features.linknewdevice.impl.screens.desktop
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
@@ -16,7 +14,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
|
||||
-3
@@ -5,8 +5,6 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.features.linknewdevice.impl.screens.number
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -16,7 +14,6 @@ import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
-3
@@ -5,8 +5,6 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.features.linknewdevice.impl.screens.qrcode
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
@@ -21,7 +19,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
-3
@@ -5,12 +5,9 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.features.linknewdevice.impl.screens.root
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
|
||||
-3
@@ -6,8 +6,6 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.features.lockscreen.impl.setup.pin
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -17,7 +15,6 @@ import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.ReadOnlyComposable
|
||||
|
||||
+10
-2
@@ -12,7 +12,10 @@ package io.element.android.features.lockscreen.impl.unlock.keypad
|
||||
|
||||
import android.view.KeyEvent
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.input.InputMode
|
||||
import androidx.compose.ui.input.InputModeManager
|
||||
import androidx.compose.ui.input.key.Key
|
||||
import androidx.compose.ui.platform.LocalInputModeManager
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.hasContentDescription
|
||||
@@ -52,7 +55,9 @@ class PinKeypadTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `typing using the hardware keyboard emits the expected events`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PinKeypadModel>()
|
||||
setPinKeyPad(onClick = eventsRecorder)
|
||||
val inputModeManager = setPinKeyPad(onClick = eventsRecorder)
|
||||
// Like a hardware keyboard would do, leave the touch mode, else the keys are not focusable.
|
||||
runOnIdle { inputModeManager.requestInputMode(InputMode.Keyboard) }
|
||||
onNodeWithText("1").requestFocus()
|
||||
onAllNodes(isRoot())[0].performKeyInput {
|
||||
val keys = listOf(
|
||||
@@ -115,13 +120,16 @@ class PinKeypadTest : RobolectricTest() {
|
||||
|
||||
private fun AndroidComposeUiTest<ComponentActivity>.setPinKeyPad(
|
||||
onClick: (PinKeypadModel) -> Unit = EnsureNeverCalledWithParam(),
|
||||
) {
|
||||
): InputModeManager {
|
||||
lateinit var inputModeManager: InputModeManager
|
||||
setContent {
|
||||
inputModeManager = LocalInputModeManager.current
|
||||
PinKeypad(
|
||||
onClick = onClick,
|
||||
maxWidth = 1000.dp,
|
||||
maxHeight = 1000.dp,
|
||||
)
|
||||
}
|
||||
return inputModeManager
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -25,6 +25,7 @@ import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.SingleIn
|
||||
import io.element.android.features.login.impl.BuildConfig
|
||||
import io.element.android.libraries.androidutils.service.ServiceBinder
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.log.logger.LoggerTag
|
||||
import io.element.android.libraries.core.uri.ensureProtocol
|
||||
import io.element.android.libraries.di.annotations.AppCoroutineScope
|
||||
@@ -69,6 +70,7 @@ class DefaultElementClassicConnection(
|
||||
private val coroutineScope: CoroutineScope,
|
||||
private val matrixAuthenticationService: MatrixAuthenticationService,
|
||||
private val homeServerLoginCompatibilityChecker: HomeServerLoginCompatibilityChecker,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) : ElementClassicConnection {
|
||||
// Messenger for communicating with the service.
|
||||
private var messenger: Messenger? = null
|
||||
@@ -221,7 +223,7 @@ class DefaultElementClassicConnection(
|
||||
fun onSessionReceived(data: Bundle) {
|
||||
// The data must be extracted from the bundle before we launch the coroutine, else the bundle will be emptied
|
||||
val state = data.toElementClassicConnectionState()
|
||||
coroutineScope.launch {
|
||||
coroutineScope.launch(dispatchers.io) {
|
||||
val updatedState = ensureHomeserverIsSupported(state)
|
||||
emitState(updatedState)
|
||||
val userId = (updatedState as? ElementClassicConnectionState.ElementClassicReady)?.elementClassicSession?.userId
|
||||
|
||||
-3
@@ -6,8 +6,6 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.features.login.impl.screens.chooseaccountprovider
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -21,7 +19,6 @@ import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
|
||||
-2
@@ -20,7 +20,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -56,7 +55,6 @@ import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LoginWithClassicView(
|
||||
state: LoginWithClassicState,
|
||||
|
||||
-2
@@ -23,7 +23,6 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -70,7 +69,6 @@ import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LoginPasswordView(
|
||||
state: LoginPasswordState,
|
||||
|
||||
+2
@@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationSer
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
@@ -499,5 +500,6 @@ class DefaultElementClassicConnectionTest : RobolectricTest() {
|
||||
coroutineScope = coroutineScope,
|
||||
matrixAuthenticationService = matrixAuthenticationService,
|
||||
homeServerLoginCompatibilityChecker = homeServerLoginCompatibilityChecker,
|
||||
dispatchers = testCoroutineDispatchers(),
|
||||
)
|
||||
}
|
||||
|
||||
-1
@@ -11,7 +11,6 @@ package io.element.android.features.logout.api.direct
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
|
||||
data class DirectLogoutState(
|
||||
val canDoDirectSignOut: Boolean,
|
||||
val logoutAction: AsyncAction<Unit>,
|
||||
val eventSink: (DirectLogoutEvent) -> Unit,
|
||||
)
|
||||
|
||||
-2
@@ -23,11 +23,9 @@ open class DirectLogoutStatePreviewParam : PreviewParameterProvider<DirectLogout
|
||||
}
|
||||
|
||||
fun aDirectLogoutState(
|
||||
canDoDirectSignOut: Boolean = true,
|
||||
logoutAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
eventSink: (DirectLogoutEvent) -> Unit = {},
|
||||
) = DirectLogoutState(
|
||||
canDoDirectSignOut = canDoDirectSignOut,
|
||||
logoutAction = logoutAction,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
@@ -31,6 +31,7 @@ dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.testtags)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
|
||||
+2
@@ -41,6 +41,7 @@ class LogoutPresenter(
|
||||
@Composable
|
||||
override fun present(): LogoutState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
val currentUser by matrixClient.userProfile.collectAsState()
|
||||
val logoutAction: MutableState<AsyncAction<Unit>> = remember {
|
||||
mutableStateOf(AsyncAction.Uninitialized)
|
||||
}
|
||||
@@ -90,6 +91,7 @@ class LogoutPresenter(
|
||||
}
|
||||
|
||||
return LogoutState(
|
||||
currentUser = currentUser,
|
||||
isLastDevice = isLastDevice,
|
||||
backupState = backupState,
|
||||
doesBackupExistOnServer = doesBackupExistOnServerAction.value.dataOrNull().orTrue(),
|
||||
|
||||
+2
@@ -12,8 +12,10 @@ import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
data class LogoutState(
|
||||
val currentUser: MatrixUser,
|
||||
val isLastDevice: Boolean,
|
||||
val backupState: BackupState,
|
||||
val doesBackupExistOnServer: Boolean,
|
||||
|
||||
+5
@@ -14,6 +14,8 @@ import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.api.encryption.SteadyStateException
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
|
||||
open class LogoutStatePreviewParam : PreviewParameterProvider<LogoutState> {
|
||||
override val values: Sequence<LogoutState>
|
||||
@@ -39,10 +41,12 @@ open class LogoutStatePreviewParam : PreviewParameterProvider<LogoutState> {
|
||||
backupUploadState = BackupUploadState.Waiting,
|
||||
waitingForALongTime = true,
|
||||
),
|
||||
aLogoutState(currentUser = aMatrixUser(id = "@alice:server.org", displayName = null)),
|
||||
)
|
||||
}
|
||||
|
||||
fun aLogoutState(
|
||||
currentUser: MatrixUser = aMatrixUser(),
|
||||
isLastDevice: Boolean = false,
|
||||
backupState: BackupState = BackupState.ENABLED,
|
||||
doesBackupExistOnServer: Boolean = true,
|
||||
@@ -52,6 +56,7 @@ fun aLogoutState(
|
||||
logoutAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
eventSink: (LogoutEvent) -> Unit = {},
|
||||
) = LogoutState(
|
||||
currentUser = currentUser,
|
||||
isLastDevice = isLastDevice,
|
||||
backupState = backupState,
|
||||
doesBackupExistOnServer = doesBackupExistOnServer,
|
||||
|
||||
+30
-3
@@ -11,7 +11,9 @@ package io.element.android.features.logout.impl
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -32,15 +34,22 @@ import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.LinearProgressIndicator
|
||||
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.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.progressIndicatorTrackColor
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.api.encryption.SteadyStateException
|
||||
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCard
|
||||
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCardMode
|
||||
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCardState
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
/**
|
||||
* Ref: https://www.figma.com/design/kEAcfun9iSpszeUDvdKZ6b/ER-351--Multi-account-in-EX?node-id=442-34581
|
||||
*/
|
||||
@Composable
|
||||
fun LogoutView(
|
||||
state: LogoutState,
|
||||
@@ -54,15 +63,17 @@ fun LogoutView(
|
||||
onBackClick = onBackClick,
|
||||
title = title(state),
|
||||
subTitle = subtitle(state),
|
||||
iconStyle = BigIcon.Style.Default(CompoundIcons.KeySolid()),
|
||||
iconStyle = BigIcon.Style.Default(CompoundIcons.Mobile()),
|
||||
modifier = modifier,
|
||||
isScrollable = true,
|
||||
buttons = {
|
||||
Buttons(
|
||||
state = state,
|
||||
onChangeRecoveryKeyClick = onChangeRecoveryKeyClick,
|
||||
onLogoutClick = {
|
||||
eventSink(LogoutEvent.Logout(ignoreSdkError = false))
|
||||
}
|
||||
},
|
||||
onCancelClick = onBackClick,
|
||||
)
|
||||
},
|
||||
) {
|
||||
@@ -71,6 +82,8 @@ fun LogoutView(
|
||||
|
||||
LogoutActionDialog(
|
||||
state.logoutAction,
|
||||
// Do not show the progress dialog, the progress is rendered in the button.
|
||||
showProgressDialog = false,
|
||||
onConfirmClick = {
|
||||
eventSink(LogoutEvent.Logout(ignoreSdkError = false))
|
||||
},
|
||||
@@ -116,6 +129,7 @@ private fun ColumnScope.Buttons(
|
||||
state: LogoutState,
|
||||
onLogoutClick: () -> Unit,
|
||||
onChangeRecoveryKeyClick: () -> Unit,
|
||||
onCancelClick: () -> Unit,
|
||||
) {
|
||||
val logoutAction = state.logoutAction
|
||||
if (state.isLastDevice) {
|
||||
@@ -139,6 +153,12 @@ private fun ColumnScope.Buttons(
|
||||
.testTag(TestTags.signOut),
|
||||
onClick = onLogoutClick,
|
||||
)
|
||||
TextButton(
|
||||
text = stringResource(id = CommonStrings.action_cancel),
|
||||
enabled = logoutAction !is AsyncAction.Loading,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onCancelClick,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -149,9 +169,16 @@ private fun Content(
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 60.dp, start = 20.dp, end = 20.dp),
|
||||
.padding(top = 8.dp, start = 16.dp, end = 16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
AccountInfoCard(
|
||||
AccountInfoCardState(
|
||||
matrixUser = state.currentUser,
|
||||
mode = AccountInfoCardMode.Simple,
|
||||
)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
when (state.backupUploadState) {
|
||||
is BackupUploadState.Uploading -> {
|
||||
LinearProgressIndicator(
|
||||
|
||||
+1
@@ -27,6 +27,7 @@ class DefaultDirectLogoutView : DirectLogoutView {
|
||||
val eventSink = state.eventSink
|
||||
LogoutActionDialog(
|
||||
state.logoutAction,
|
||||
showProgressDialog = true,
|
||||
onConfirmClick = {
|
||||
eventSink(DirectLogoutEvent.Logout(ignoreSdkError = false))
|
||||
},
|
||||
|
||||
-15
@@ -10,28 +10,22 @@ package io.element.android.features.logout.impl.direct
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutEvent
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.logout.impl.tools.isBackingUp
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Inject
|
||||
class DirectLogoutPresenter(
|
||||
private val matrixClient: MatrixClient,
|
||||
private val encryptionService: EncryptionService,
|
||||
) : Presenter<DirectLogoutState> {
|
||||
@Composable
|
||||
override fun present(): DirectLogoutState {
|
||||
@@ -41,13 +35,6 @@ class DirectLogoutPresenter(
|
||||
mutableStateOf(AsyncAction.Uninitialized)
|
||||
}
|
||||
|
||||
val backupUploadState: BackupUploadState by remember {
|
||||
encryptionService.waitForBackupUploadSteadyState()
|
||||
}
|
||||
.collectAsState(initial = BackupUploadState.Unknown)
|
||||
|
||||
val isLastDevice by encryptionService.isLastDevice.collectAsState()
|
||||
|
||||
fun handleEvent(event: DirectLogoutEvent) {
|
||||
when (event) {
|
||||
is DirectLogoutEvent.Logout -> {
|
||||
@@ -64,8 +51,6 @@ class DirectLogoutPresenter(
|
||||
}
|
||||
|
||||
return DirectLogoutState(
|
||||
canDoDirectSignOut = !isLastDevice &&
|
||||
!backupUploadState.isBackingUp(),
|
||||
logoutAction = logoutAction.value,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
|
||||
+6
-2
@@ -19,6 +19,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@Composable
|
||||
fun LogoutActionDialog(
|
||||
state: AsyncAction<Unit>,
|
||||
showProgressDialog: Boolean,
|
||||
onConfirmClick: () -> Unit,
|
||||
onForceLogoutClick: () -> Unit,
|
||||
onDismissDialog: () -> Unit,
|
||||
@@ -31,8 +32,11 @@ fun LogoutActionDialog(
|
||||
onSubmitClick = onConfirmClick,
|
||||
onDismiss = onDismissDialog
|
||||
)
|
||||
is AsyncAction.Loading ->
|
||||
ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content))
|
||||
is AsyncAction.Loading -> {
|
||||
if (showProgressDialog) {
|
||||
ProgressDialog(text = stringResource(id = R.string.screen_signout_in_progress_dialog_content))
|
||||
}
|
||||
}
|
||||
is AsyncAction.Failure ->
|
||||
RetryDialog(
|
||||
title = stringResource(id = CommonStrings.dialog_title_error),
|
||||
|
||||
+10
-1
@@ -20,7 +20,9 @@ import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||
import io.element.android.libraries.workmanager.api.WorkManagerRequestType
|
||||
@@ -39,11 +41,18 @@ class LogoutPresenterTest {
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createLogoutPresenter()
|
||||
val presenter = createLogoutPresenter(
|
||||
matrixClient = FakeMatrixClient(
|
||||
sessionId = A_SESSION_ID,
|
||||
userDisplayName = "Alice",
|
||||
userAvatarUrl = "mxc://avatar",
|
||||
),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.currentUser).isEqualTo(MatrixUser(A_SESSION_ID, "Alice", "mxc://avatar"))
|
||||
assertThat(initialState.isLastDevice).isFalse()
|
||||
assertThat(initialState.backupState).isEqualTo(BackupState.UNKNOWN)
|
||||
assertThat(initialState.doesBackupExistOnServer).isTrue()
|
||||
|
||||
+32
-1
@@ -13,8 +13,11 @@ package io.element.android.features.logout.impl
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
@@ -25,8 +28,22 @@ import io.element.android.tests.testutils.pressBack
|
||||
import io.element.android.tests.testutils.pressTag
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
class LogoutViewTest : RobolectricTest() {
|
||||
@Test
|
||||
@Config(qualifiers = "h1024dp")
|
||||
fun `the current user is displayed`() = runAndroidComposeUiTest {
|
||||
setLogoutView(
|
||||
aLogoutState(
|
||||
currentUser = aMatrixUser(id = "@alice:server.org", displayName = "Alice"),
|
||||
eventSink = EventsRecorder(expectEvents = false),
|
||||
),
|
||||
)
|
||||
onNodeWithText("Alice").assertIsDisplayed()
|
||||
onNodeWithText("@alice:server.org").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on logout sends a LogoutEvent`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<LogoutEvent>()
|
||||
@@ -88,10 +105,24 @@ class LogoutViewTest : RobolectricTest() {
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
)
|
||||
clickOn(CommonStrings.action_cancel)
|
||||
clickOn(CommonStrings.action_cancel, inDialog = true)
|
||||
eventsRecorder.assertSingle(LogoutEvent.CloseDialogs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on cancel invoke back callback`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<LogoutEvent>(expectEvents = false)
|
||||
ensureCalledOnce { callback ->
|
||||
setLogoutView(
|
||||
aLogoutState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackClick = callback,
|
||||
)
|
||||
clickOn(CommonStrings.action_cancel)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `last session setting button invoke onChangeRecoveryKeyClicked`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<LogoutEvent>(expectEvents = false)
|
||||
|
||||
-44
@@ -16,13 +16,9 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutEvent
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -38,44 +34,6 @@ class DirectLogoutPresenterTest {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.canDoDirectSignOut).isTrue()
|
||||
assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - initial state - last session`() = runTest {
|
||||
val presenter = createDirectLogoutPresenter(
|
||||
encryptionService = FakeEncryptionService().apply {
|
||||
emitIsLastDevice(true)
|
||||
}
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.canDoDirectSignOut).isFalse()
|
||||
assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - initial state - backing up`() = runTest {
|
||||
val encryptionService = FakeEncryptionService()
|
||||
encryptionService.givenWaitForBackupUploadSteadyStateFlow(
|
||||
flow {
|
||||
emit(BackupUploadState.Waiting)
|
||||
}
|
||||
)
|
||||
val presenter = createDirectLogoutPresenter(
|
||||
encryptionService = encryptionService
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.canDoDirectSignOut).isFalse()
|
||||
assertThat(initialState.logoutAction).isEqualTo(AsyncAction.Uninitialized)
|
||||
}
|
||||
}
|
||||
@@ -180,9 +138,7 @@ class DirectLogoutPresenterTest {
|
||||
|
||||
private fun createDirectLogoutPresenter(
|
||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||
): DirectLogoutPresenter = DirectLogoutPresenter(
|
||||
matrixClient = matrixClient,
|
||||
encryptionService = encryptionService,
|
||||
)
|
||||
}
|
||||
|
||||
+1
-3
@@ -28,7 +28,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -92,7 +91,6 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
/**
|
||||
* Ref: https://www.figma.com/design/zftpgS6LjiczobJZ1GUNpt/Updates-to-Media---File-Upload?node-id=51-3514
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AttachmentsPreviewView(
|
||||
state: AttachmentsPreviewState,
|
||||
@@ -263,7 +261,7 @@ private fun AttachmentSendStateView(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun AttachmentPreviewContent(
|
||||
state: AttachmentsPreviewState,
|
||||
|
||||
-2
@@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -82,7 +81,6 @@ private val maxHandleTouchRadius = 56.dp
|
||||
/**
|
||||
* Ref: https://www.figma.com/design/zftpgS6LjiczobJZ1GUNpt/Updates-to-Media---File-Upload?node-id=51-3539
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AttachmentImageEditorView(
|
||||
state: AttachmentImageEditorState,
|
||||
|
||||
-2
@@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.plus
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -107,7 +106,6 @@ fun PinnedMessagesListView(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun PinnedMessagesListTopBar(
|
||||
state: PinnedMessagesListState,
|
||||
|
||||
-2
@@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -43,7 +42,6 @@ import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ReportMessageView(
|
||||
state: ReportMessageState,
|
||||
|
||||
-2
@@ -23,7 +23,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
@@ -74,7 +73,6 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ThreadsListView(
|
||||
state: ThreadsListState,
|
||||
|
||||
-2
@@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -57,7 +56,6 @@ import org.json.JSONObject
|
||||
* Screen used to display debug info for events.
|
||||
* It will only be available in debug builds.
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun EventDebugInfoView(
|
||||
eventId: EventId?,
|
||||
|
||||
-2
@@ -22,7 +22,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -61,7 +60,6 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun MessagesViewTopBar(
|
||||
roomName: String?,
|
||||
|
||||
-2
@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -43,7 +42,6 @@ import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun ThreadTopBar(
|
||||
roomName: String?,
|
||||
|
||||
-2
@@ -22,7 +22,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -216,7 +215,6 @@ fun CreatePollView(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun CreatePollTopAppBar(
|
||||
mode: CreatePollState.Mode,
|
||||
|
||||
-3
@@ -23,7 +23,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -53,7 +52,6 @@ import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun PollHistoryView(
|
||||
state: PollHistoryState,
|
||||
@@ -125,7 +123,6 @@ fun PollHistoryView(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun PollHistoryFilterButtons(
|
||||
activeFilter: PollHistoryFilter,
|
||||
|
||||
-5
@@ -17,7 +17,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.enterprise.api.SessionEnterpriseService
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
@@ -34,7 +33,6 @@ class PreferencesAccountPresenter(
|
||||
private val sessionVerificationService: SessionVerificationService,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
private val indicatorService: IndicatorService,
|
||||
private val directLogoutPresenter: Presenter<DirectLogoutState>,
|
||||
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val sessionEnterpriseService: SessionEnterpriseService,
|
||||
@@ -65,8 +63,6 @@ class PreferencesAccountPresenter(
|
||||
canDeactivateAccount = matrixClient.canDeactivateAccount()
|
||||
}
|
||||
|
||||
val directLogoutState = directLogoutPresenter.present()
|
||||
|
||||
val accountManagementUrl by produceState<String?>(initialValue = null) {
|
||||
value = matrixClient.getAccountManagementUrl(null)
|
||||
.getOrNull()
|
||||
@@ -83,7 +79,6 @@ class PreferencesAccountPresenter(
|
||||
canReportBug = canReportBug,
|
||||
showLinkNewDevice = showLinkNewDevice,
|
||||
canDeactivateAccount = canDeactivateAccount,
|
||||
directLogoutState = directLogoutState,
|
||||
snackbarMessage = snackbarMessage,
|
||||
)
|
||||
}
|
||||
|
||||
-2
@@ -7,7 +7,6 @@
|
||||
|
||||
package io.element.android.features.preferences.impl.account
|
||||
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
@@ -19,6 +18,5 @@ data class PreferencesAccountState(
|
||||
val canReportBug: Boolean,
|
||||
val showLinkNewDevice: Boolean,
|
||||
val canDeactivateAccount: Boolean,
|
||||
val directLogoutState: DirectLogoutState,
|
||||
val snackbarMessage: SnackbarMessage?,
|
||||
)
|
||||
|
||||
-4
@@ -8,8 +8,6 @@
|
||||
package io.element.android.features.preferences.impl.account
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
@@ -56,7 +54,6 @@ fun aPreferencesAccountState(
|
||||
canReportBug: Boolean = false,
|
||||
showLinkNewDevice: Boolean = false,
|
||||
canDeactivateAccount: Boolean = false,
|
||||
directLogoutState: DirectLogoutState = aDirectLogoutState(),
|
||||
snackbarMessage: SnackbarMessage? = null,
|
||||
) = PreferencesAccountState(
|
||||
myUser = myUser,
|
||||
@@ -66,6 +63,5 @@ fun aPreferencesAccountState(
|
||||
canReportBug = canReportBug,
|
||||
showLinkNewDevice = showLinkNewDevice,
|
||||
canDeactivateAccount = canDeactivateAccount,
|
||||
directLogoutState = directLogoutState,
|
||||
snackbarMessage = snackbarMessage,
|
||||
)
|
||||
|
||||
-2
@@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -38,7 +37,6 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.MatrixUserRow
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun BlockedUsersView(
|
||||
state: BlockedUsersState,
|
||||
|
||||
-2
@@ -17,7 +17,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -40,7 +39,6 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
* The contents of the Labs screen.
|
||||
* Design: https://www.figma.com/design/V0dkfRAW6T3yCQKjahpzkX/ER-46-EX--Threads?node-id=2004-27319&t=yssy1yYYigsGON3s-0
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LabsView(
|
||||
state: LabsState,
|
||||
|
||||
+1
-12
@@ -19,8 +19,6 @@ import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutEvent
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutView
|
||||
import io.element.android.features.preferences.impl.account.PreferencesAccountCallback
|
||||
import io.element.android.libraries.androidutils.browser.openUrlInChromeCustomTab
|
||||
import io.element.android.libraries.architecture.callback
|
||||
@@ -34,7 +32,6 @@ class PreferencesRootNode(
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val presenter: PreferencesRootPresenter,
|
||||
private val emojiPickerRenderer: EmojiPickerRenderer,
|
||||
private val directLogoutView: DirectLogoutView,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
interface Callback : PreferencesAccountCallback, Plugin {
|
||||
fun navigateToAddAccount()
|
||||
@@ -89,16 +86,8 @@ class PreferencesRootNode(
|
||||
onManageAccountClick = { onManageAccountClick(activity, it, isDark) },
|
||||
onOpenNotificationSettings = callback::navigateToNotificationSettings,
|
||||
onEditProfileClick = callback::navigateToUserProfile,
|
||||
onSignOutClick = {
|
||||
if (state.preferencesAccountState.directLogoutState.canDoDirectSignOut) {
|
||||
state.preferencesAccountState.directLogoutState.eventSink(DirectLogoutEvent.Logout(ignoreSdkError = false))
|
||||
} else {
|
||||
callback.startSignOutFlow()
|
||||
}
|
||||
},
|
||||
onSignOutClick = callback::startSignOutFlow,
|
||||
onDeactivateClick = callback::startAccountDeactivationFlow
|
||||
)
|
||||
|
||||
directLogoutView.Render(state = state.preferencesAccountState.directLogoutState)
|
||||
}
|
||||
}
|
||||
|
||||
-2
@@ -18,7 +18,6 @@ import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -56,7 +55,6 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
/**
|
||||
* https://www.figma.com/design/pDlJZGBsri47FNTXMnEdXB/Compound-Android-Templates?node-id=3182-36115&t=U1vS3px9HzlzWYd7-4
|
||||
*/
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun EditUserProfileView(
|
||||
state: EditUserProfileState,
|
||||
|
||||
-3
@@ -10,7 +10,6 @@ package io.element.android.features.preferences.impl.account
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.enterprise.api.SessionEnterpriseService
|
||||
import io.element.android.features.enterprise.test.FakeSessionEnterpriseService
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
@@ -65,7 +64,6 @@ class PreferencesAccountPresenterTest {
|
||||
assertThat(initialState.showSecureBackupBadge).isFalse()
|
||||
assertThat(initialState.accountManagementUrl).isNull()
|
||||
assertThat(initialState.showLinkNewDevice).isFalse()
|
||||
assertThat(initialState.directLogoutState).isEqualTo(aDirectLogoutState())
|
||||
assertThat(initialState.snackbarMessage).isNull()
|
||||
val loadedState = consumeItemsUntilPredicate { it.accountManagementUrl != null }.last()
|
||||
assertThat(loadedState.showSecureBackup).isFalse()
|
||||
@@ -169,7 +167,6 @@ class PreferencesAccountPresenterTest {
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
snackbarDispatcher = SnackbarDispatcher(),
|
||||
indicatorService = indicatorService,
|
||||
directLogoutPresenter = { aDirectLogoutState() },
|
||||
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
||||
featureFlagService = featureFlagService,
|
||||
sessionEnterpriseService = sessionEnterpriseService,
|
||||
|
||||
-2
@@ -19,7 +19,6 @@ import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
@@ -40,7 +39,6 @@ import io.element.android.libraries.designsystem.theme.components.TextField
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ReportRoomView(
|
||||
state: ReportRoomState,
|
||||
|
||||
-2
@@ -12,7 +12,6 @@ import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -31,7 +30,6 @@ import io.element.android.libraries.designsystem.theme.components.TextButton
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ChangeRoomPermissionsView(
|
||||
state: ChangeRoomPermissionsState,
|
||||
|
||||
-2
@@ -28,7 +28,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -72,7 +71,6 @@ import io.element.android.libraries.matrix.ui.model.getAvatarData
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ChangeRolesView(
|
||||
state: ChangeRolesState,
|
||||
|
||||
-2
@@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -132,7 +131,6 @@ private fun RoomAliasResolverContent(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun RoomAliasResolverTopBar(
|
||||
onBackClick: () -> Unit,
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
id("io.element.android-compose-library")
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.roomdetails.api
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
/** Optional room-details sections contributed by a build-specific implementation. */
|
||||
fun interface RoomDetailsExtension {
|
||||
@Composable
|
||||
fun ColumnScope.Render(modifier: Modifier)
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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.roomdetails.impl
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.features.roomdetails.api.RoomDetailsExtension
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
|
||||
@ContributesBinding(RoomScope::class)
|
||||
class NoopRoomDetailsExtension : RoomDetailsExtension {
|
||||
@Composable
|
||||
override fun ColumnScope.Render(modifier: Modifier) = Unit
|
||||
}
|
||||
+3
@@ -24,6 +24,7 @@ import dev.zacsweers.metro.AssistedInject
|
||||
import im.vector.app.features.analytics.plan.MobileScreen
|
||||
import io.element.android.annotations.ContributesNode
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomRenderer
|
||||
import io.element.android.features.roomdetails.api.RoomDetailsExtension
|
||||
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent
|
||||
import io.element.android.libraries.architecture.appyx.launchMolecule
|
||||
import io.element.android.libraries.architecture.callback
|
||||
@@ -46,6 +47,7 @@ class RoomDetailsNode(
|
||||
private val room: BaseRoom,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val leaveRoomRenderer: LeaveRoomRenderer,
|
||||
private val roomDetailsExtension: RoomDetailsExtension,
|
||||
) : Node(buildContext, plugins = plugins), RoomDetailsNavigator {
|
||||
interface Callback : Plugin {
|
||||
fun navigateBack()
|
||||
@@ -120,6 +122,7 @@ class RoomDetailsNode(
|
||||
|
||||
RoomDetailsView(
|
||||
state = state,
|
||||
additionalSections = { with(roomDetailsExtension) { Render(Modifier) } },
|
||||
modifier = modifier,
|
||||
goBack = ::navigateUp,
|
||||
onActionClick = ::onActionClick,
|
||||
|
||||
+4
-2
@@ -14,6 +14,7 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
@@ -24,7 +25,6 @@ import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -124,6 +124,7 @@ fun RoomDetailsView(
|
||||
onProfileClick: (UserId) -> Unit,
|
||||
onReportRoomClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
additionalSections: @Composable ColumnScope.() -> Unit = {},
|
||||
leaveRoomView: @Composable () -> Unit,
|
||||
) {
|
||||
val snackbarHostState = rememberSnackbarHostState(snackbarMessage = state.snackbarMessage)
|
||||
@@ -199,6 +200,8 @@ fun RoomDetailsView(
|
||||
)
|
||||
}
|
||||
|
||||
additionalSections()
|
||||
|
||||
PreferenceCategory {
|
||||
if (state.hasNewContent) {
|
||||
ListItem(
|
||||
@@ -356,7 +359,6 @@ private fun KnockRequestsItem(knockRequestsCount: Int?, onKnockRequestsClick: ()
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun RoomDetailsTopBar(
|
||||
goBack: () -> Unit,
|
||||
|
||||
-2
@@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -76,7 +75,6 @@ fun RoomInviteMembersView(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun RoomInviteMembersTopBar(
|
||||
canSend: Boolean,
|
||||
|
||||
-2
@@ -27,7 +27,6 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
@@ -352,7 +351,6 @@ private fun RoomMemberListItem(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun RoomMemberListTopBar(
|
||||
canInvite: Boolean,
|
||||
|
||||
-2
@@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -160,7 +159,6 @@ private fun RoomSpecificNotificationSettingsView(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun RoomNotificationSettingsTopBar(
|
||||
onBackClick: () -> Unit,
|
||||
|
||||
-2
@@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -90,7 +89,6 @@ fun UserDefinedRoomNotificationSettingsView(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun UserDefinedRoomNotificationSettingsTopBar(
|
||||
roomName: String,
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
<string name="screen_room_change_role_unsaved_changes_description">"You have unsaved changes."</string>
|
||||
<string name="screen_room_change_role_unsaved_changes_title">"Save changes?"</string>
|
||||
<string name="screen_room_details_add_topic_title">"Add topic"</string>
|
||||
<string name="screen_room_details_audio_push_to_talk">"Push to talk"</string>
|
||||
<string name="screen_room_details_audio_title">"Room Audio"</string>
|
||||
<string name="screen_room_details_audio_video_calls">"Video calls"</string>
|
||||
<string name="screen_room_details_badge_encrypted">"Encrypted"</string>
|
||||
<string name="screen_room_details_badge_not_encrypted">"Not encrypted"</string>
|
||||
<string name="screen_room_details_badge_public">"Public room"</string>
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalTestApi::class)
|
||||
|
||||
package io.element.android.features.roomdetails.impl
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.text.BasicText
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.assertCountEquals
|
||||
import androidx.compose.ui.test.onChildren
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.onRoot
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import org.junit.Test
|
||||
|
||||
class NoopRoomDetailsExtensionTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `default extension renders no additional content`() = runAndroidComposeUiTest<ComponentActivity> {
|
||||
val extension = NoopRoomDetailsExtension()
|
||||
setContent { Column { with(extension) { Render(Modifier) } } }
|
||||
onRoot().onChildren().assertCountEquals(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `default extension leaves surrounding content unchanged during updates`() = runAndroidComposeUiTest<ComponentActivity> {
|
||||
val extension = NoopRoomDetailsExtension()
|
||||
val text = mutableStateOf("Before")
|
||||
setContent {
|
||||
Column {
|
||||
BasicText(text.value)
|
||||
with(extension) { Render(Modifier) }
|
||||
}
|
||||
}
|
||||
onNodeWithText("Before").assertExists()
|
||||
onRoot().onChildren().assertCountEquals(1)
|
||||
runOnIdle { text.value = "After" }
|
||||
onNodeWithText("After").assertExists()
|
||||
onRoot().onChildren().assertCountEquals(1)
|
||||
}
|
||||
}
|
||||
+12
@@ -11,16 +11,20 @@
|
||||
package io.element.android.features.roomdetails.impl
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onLast
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import io.element.android.features.roomdetails.impl.members.aRoomMember
|
||||
import io.element.android.features.userprofile.shared.aUserProfileState
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.notification.CallIntent
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
@@ -41,6 +45,12 @@ import org.junit.Test
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
class RoomDetailsViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `extension section is rendered`() = runAndroidComposeUiTest {
|
||||
setRoomDetailView(additionalSections = { Text("Extra section") })
|
||||
onNodeWithText("Extra section").assertExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `click on back invokes expected callback`() = runAndroidComposeUiTest {
|
||||
ensureCalledOnce { callback ->
|
||||
@@ -381,6 +391,7 @@ private fun AndroidComposeUiTest<ComponentActivity>.setRoomDetailView(
|
||||
onSecurityAndPrivacyClick: () -> Unit = EnsureNeverCalled(),
|
||||
onProfileClick: (UserId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onReportRoomClick: () -> Unit = EnsureNeverCalled(),
|
||||
additionalSections: @Composable ColumnScope.() -> Unit = {},
|
||||
) {
|
||||
setContent {
|
||||
RoomDetailsView(
|
||||
@@ -402,6 +413,7 @@ private fun AndroidComposeUiTest<ComponentActivity>.setRoomDetailView(
|
||||
onProfileClick = onProfileClick,
|
||||
onReportRoomClick = onReportRoomClick,
|
||||
leaveRoomView = {},
|
||||
additionalSections = additionalSections,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
-3
@@ -6,8 +6,6 @@
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.features.roomdetailsedit.impl
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
@@ -19,7 +17,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
||||
-2
@@ -21,7 +21,6 @@ import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.TextFieldColors
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -83,7 +82,6 @@ fun RoomDirectoryView(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun RoomDirectoryTopBar(
|
||||
onBackClick: () -> Unit,
|
||||
|
||||
-2
@@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -87,7 +86,6 @@ fun EditRoomAddressView(
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun EditRoomAddressTopBar(
|
||||
isSaveActionEnabled: Boolean,
|
||||
|
||||
-2
@@ -12,7 +12,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -166,7 +165,6 @@ private fun CheckableSpaceListItem(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ManageAuthorizedSpacesTopBar(
|
||||
isDoneButtonEnabled: Boolean,
|
||||
|
||||
-2
@@ -22,7 +22,6 @@ import androidx.compose.foundation.progressSemantics
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.selection.selectableGroup
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -156,7 +155,6 @@ fun SecurityAndPrivacyView(
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun SecurityAndPrivacyToolbar(
|
||||
isSaveActionEnabled: Boolean,
|
||||
|
||||
@@ -16,6 +16,12 @@ plugins {
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.signedout.impl"
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupDependencyInjection()
|
||||
@@ -30,7 +36,7 @@ dependencies {
|
||||
implementation(projects.libraries.sessionStorage.api)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
|
||||
testCommonDependencies(libs)
|
||||
testCommonDependencies(libs, true)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.sessionStorage.test)
|
||||
}
|
||||
|
||||
+1
-1
@@ -9,5 +9,5 @@
|
||||
package io.element.android.features.signedout.impl
|
||||
|
||||
sealed interface SignedOutEvent {
|
||||
data object SignInAgain : SignedOutEvent
|
||||
data object Submit : SignedOutEvent
|
||||
}
|
||||
|
||||
+17
-9
@@ -17,17 +17,18 @@ import dev.zacsweers.metro.Assisted
|
||||
import dev.zacsweers.metro.AssistedFactory
|
||||
import dev.zacsweers.metro.AssistedInject
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@AssistedInject
|
||||
class SignedOutPresenter(
|
||||
@Assisted private val sessionId: SessionId,
|
||||
private val sessionStore: SessionStore,
|
||||
private val buildMeta: BuildMeta,
|
||||
) : Presenter<SignedOutState> {
|
||||
@AssistedFactory
|
||||
fun interface Factory {
|
||||
@@ -37,24 +38,31 @@ class SignedOutPresenter(
|
||||
@Composable
|
||||
override fun present(): SignedOutState {
|
||||
val signedOutSession by remember {
|
||||
sessionStore.sessionsFlow().map { sessions ->
|
||||
sessions.firstOrNull { it.userId == sessionId.value }
|
||||
}
|
||||
}.collectAsState(initial = null)
|
||||
sessionStore.sessionsFlow()
|
||||
// Ignore the removal of the session, to keep rendering the account data until the screen is closed
|
||||
.mapNotNull { sessions -> sessions.firstOrNull { it.userId == sessionId.value } }
|
||||
.map { it.toMatrixUser() }
|
||||
}.collectAsState(initial = MatrixUser(userId = sessionId))
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
fun handleEvent(event: SignedOutEvent) {
|
||||
when (event) {
|
||||
SignedOutEvent.SignInAgain -> coroutineScope.launch {
|
||||
SignedOutEvent.Submit -> coroutineScope.launch {
|
||||
sessionStore.removeSession(sessionId.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SignedOutState(
|
||||
appName = buildMeta.applicationName,
|
||||
signedOutSession = signedOutSession,
|
||||
signedOutMatrixUser = signedOutSession,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
|
||||
private fun SessionData.toMatrixUser() = MatrixUser(
|
||||
userId = sessionId,
|
||||
displayName = userDisplayName,
|
||||
avatarUrl = userAvatarUrl,
|
||||
avatarThumbnail = userAvatarData,
|
||||
)
|
||||
}
|
||||
|
||||
+2
-3
@@ -8,10 +8,9 @@
|
||||
|
||||
package io.element.android.features.signedout.impl
|
||||
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
|
||||
data class SignedOutState(
|
||||
val appName: String,
|
||||
val signedOutSession: SessionData?,
|
||||
val signedOutMatrixUser: MatrixUser,
|
||||
val eventSink: (SignedOutEvent) -> Unit,
|
||||
)
|
||||
|
||||
+9
-32
@@ -9,44 +9,21 @@
|
||||
package io.element.android.features.signedout.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.sessionstorage.api.LoginType
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||
|
||||
open class SignedOutStatePreviewParam : PreviewParameterProvider<SignedOutState> {
|
||||
override val values: Sequence<SignedOutState>
|
||||
get() = sequenceOf(
|
||||
aSignedOutState(),
|
||||
// Add other states here
|
||||
aSignedOutState(signedOutSession = aMatrixUser(id = "@alice:server.org", displayName = null)),
|
||||
)
|
||||
}
|
||||
|
||||
private fun aSignedOutState() = SignedOutState(
|
||||
appName = "AppName",
|
||||
signedOutSession = aSessionData(),
|
||||
eventSink = {},
|
||||
internal fun aSignedOutState(
|
||||
signedOutSession: MatrixUser = aMatrixUser(id = "@alice:server.org", displayName = "Alice"),
|
||||
eventSink: (SignedOutEvent) -> Unit = {},
|
||||
) = SignedOutState(
|
||||
signedOutMatrixUser = signedOutSession,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
private fun aSessionData(
|
||||
sessionId: String = "@alice:server.org",
|
||||
isTokenValid: Boolean = false,
|
||||
): SessionData {
|
||||
return SessionData(
|
||||
userId = sessionId,
|
||||
deviceId = "aDeviceId",
|
||||
accessToken = "anAccessToken",
|
||||
refreshToken = "aRefreshToken",
|
||||
homeserverUrl = "aHomeserverUrl",
|
||||
oAuthData = null,
|
||||
loginTimestamp = null,
|
||||
isTokenValid = isTokenValid,
|
||||
loginType = LoginType.UNKNOWN,
|
||||
passphrase = null,
|
||||
sessionPath = "/a/path/to/a/session",
|
||||
cachePath = "/a/path/to/a/cache",
|
||||
position = 0,
|
||||
lastUsageIndex = 0,
|
||||
userDisplayName = null,
|
||||
userAvatarUrl = null,
|
||||
userAvatarData = null,
|
||||
)
|
||||
}
|
||||
|
||||
+38
-21
@@ -9,14 +9,14 @@
|
||||
package io.element.android.features.signedout.impl
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.BiasAlignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
@@ -31,58 +31,75 @@ import io.element.android.libraries.designsystem.components.visuallist.VisualLis
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCard
|
||||
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCardMode
|
||||
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCardState
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* Ref: https://www.figma.com/design/kEAcfun9iSpszeUDvdKZ6b/ER-351--Multi-account-in-EX?node-id=856-6104
|
||||
*/
|
||||
@Composable
|
||||
fun SignedOutView(
|
||||
state: SignedOutState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BackHandler(onBack = { state.eventSink(SignedOutEvent.SignInAgain) })
|
||||
BackHandler(onBack = { state.eventSink(SignedOutEvent.Submit) })
|
||||
HeaderFooterPage(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding()
|
||||
.imePadding(),
|
||||
header = { SignedOutHeader(state) },
|
||||
content = { SignedOutContent() },
|
||||
isScrollable = true,
|
||||
header = { SignedOutHeader() },
|
||||
content = { SignedOutContent(state.signedOutMatrixUser) },
|
||||
footer = {
|
||||
SignedOutFooter(
|
||||
onSignInAgain = { state.eventSink(SignedOutEvent.SignInAgain) },
|
||||
onSubmit = { state.eventSink(SignedOutEvent.Submit) },
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SignedOutHeader(state: SignedOutState) {
|
||||
private fun SignedOutHeader() {
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = Modifier.padding(top = 60.dp, bottom = 12.dp),
|
||||
title = stringResource(id = R.string.screen_signed_out_title),
|
||||
subTitle = stringResource(id = R.string.screen_signed_out_subtitle, state.appName),
|
||||
iconStyle = BigIcon.Style.Default(CompoundIcons.UserProfileSolid()),
|
||||
subTitle = stringResource(id = R.string.screen_signed_out_subtitle_v2),
|
||||
iconStyle = BigIcon.Style.Default(
|
||||
vectorIcon = CompoundIcons.Devices(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SignedOutContent() {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = BiasAlignment(
|
||||
horizontalBias = 0f,
|
||||
verticalBias = -0.4f
|
||||
)
|
||||
private fun SignedOutContent(
|
||||
signedOutMatrixUser: MatrixUser,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||
) {
|
||||
AccountInfoCard(
|
||||
AccountInfoCardState(
|
||||
matrixUser = signedOutMatrixUser,
|
||||
mode = AccountInfoCardMode.Simple,
|
||||
)
|
||||
)
|
||||
VisualList(
|
||||
items = persistentListOf(
|
||||
VisualListItemData(
|
||||
message = stringResource(id = R.string.screen_signed_out_reason_1),
|
||||
iconVector = CompoundIcons.Lock(),
|
||||
iconVector = CompoundIcons.UserProfile(),
|
||||
),
|
||||
VisualListItemData(
|
||||
message = stringResource(id = R.string.screen_signed_out_reason_2),
|
||||
iconVector = CompoundIcons.Devices(),
|
||||
iconVector = CompoundIcons.Lock(),
|
||||
),
|
||||
VisualListItemData(
|
||||
message = stringResource(id = R.string.screen_signed_out_reason_3),
|
||||
@@ -95,12 +112,12 @@ private fun SignedOutContent() {
|
||||
|
||||
@Composable
|
||||
private fun SignedOutFooter(
|
||||
onSignInAgain: () -> Unit,
|
||||
onSubmit: () -> Unit,
|
||||
) {
|
||||
ButtonColumnMolecule {
|
||||
Button(
|
||||
text = stringResource(id = CommonStrings.action_sign_in_again),
|
||||
onClick = onSignInAgain,
|
||||
text = stringResource(id = CommonStrings.action_ok),
|
||||
onClick = onSubmit,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_signed_out_reason_1">"You’ve changed your password on another session"</string>
|
||||
<string name="screen_signed_out_reason_2">"You have deleted the session from another session"</string>
|
||||
<string name="screen_signed_out_reason_3">"Your server’s administrator has invalidated your access"</string>
|
||||
<string name="screen_signed_out_reason_1">"You removed this device from your account"</string>
|
||||
<string name="screen_signed_out_reason_2">"You’ve changed your password on another device"</string>
|
||||
<string name="screen_signed_out_reason_3">"Your server\'s session expired or the administrator has blocked your access"</string>
|
||||
<string name="screen_signed_out_subtitle">"You might have been signed out for one of the reasons listed below. Please sign in again to continue using %s."</string>
|
||||
<string name="screen_signed_out_title">"You’re signed out"</string>
|
||||
<string name="screen_signed_out_subtitle_v2">"This device was removed for one of the reasons listed below. Please sign in again to connect this device to your account."</string>
|
||||
<string name="screen_signed_out_title">"Device removed"</string>
|
||||
</resources>
|
||||
|
||||
+28
-11
@@ -13,13 +13,14 @@ import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.matrix.test.AN_APPLICATION_NAME
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
@@ -30,7 +31,12 @@ class SignedOutPresenterTest {
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val aSessionData = aSessionData()
|
||||
val aSessionData = aSessionData(
|
||||
sessionId = A_SESSION_ID.value,
|
||||
userDisplayName = "Alice",
|
||||
userAvatarUrl = "avatarUrl",
|
||||
userAvatarData = "avatarData",
|
||||
)
|
||||
val sessionStore = InMemorySessionStore(
|
||||
initialList = listOf(aSessionData)
|
||||
)
|
||||
@@ -38,16 +44,26 @@ class SignedOutPresenterTest {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.appName).isEqualTo(AN_APPLICATION_NAME)
|
||||
assertThat(initialState.signedOutSession).isEqualTo(aSessionData)
|
||||
assertThat(initialState.signedOutMatrixUser).isEqualTo(MatrixUser(userId = A_SESSION_ID))
|
||||
assertThat(awaitItem().signedOutMatrixUser).isEqualTo(
|
||||
MatrixUser(
|
||||
userId = A_SESSION_ID,
|
||||
displayName = "Alice",
|
||||
avatarUrl = "avatarUrl",
|
||||
avatarThumbnail = "avatarData",
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `present - sign in again`() = runTest {
|
||||
val aSessionData = aSessionData()
|
||||
val aSessionData = aSessionData(
|
||||
sessionId = A_SESSION_ID.value,
|
||||
userDisplayName = "Alice",
|
||||
)
|
||||
val sessionStore = InMemorySessionStore(
|
||||
initialList = listOf(aSessionData)
|
||||
)
|
||||
@@ -57,13 +73,15 @@ class SignedOutPresenterTest {
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.signedOutSession).isEqualTo(aSessionData)
|
||||
assertThat(initialState.signedOutMatrixUser).isEqualTo(MatrixUser(userId = A_SESSION_ID, displayName = "Alice"))
|
||||
assertThat(sessionStore.getAllSessions()).isNotEmpty()
|
||||
assertThat(sessionStore.numberOfSessions()).isEqualTo(1)
|
||||
initialState.eventSink(SignedOutEvent.SignInAgain)
|
||||
assertThat(awaitItem().signedOutSession).isNull()
|
||||
initialState.eventSink(SignedOutEvent.Submit)
|
||||
runCurrent()
|
||||
assertThat(sessionStore.getAllSessions()).isEmpty()
|
||||
assertThat(sessionStore.numberOfSessions()).isEqualTo(0)
|
||||
// The removal of the session must not change the rendered data
|
||||
expectNoEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,6 +93,5 @@ internal fun createSignedOutPresenter(
|
||||
return SignedOutPresenter(
|
||||
sessionId = sessionId,
|
||||
sessionStore = sessionStore,
|
||||
buildMeta = aBuildMeta(applicationName = AN_APPLICATION_NAME),
|
||||
)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user