Merge pull request #2733 from element-hq/feature/bma/disableKnock
Disable knock
This commit is contained in:
@@ -43,8 +43,8 @@ import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMo
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.OnboardingBackground
|
||||
import io.element.android.libraries.designsystem.components.PageTitle
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
||||
@@ -41,8 +41,8 @@ import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.ftue.impl.R
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.background.OnboardingBackground
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.components.OnboardingBackground
|
||||
import io.element.android.libraries.designsystem.components.PageTitle
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
|
||||
@@ -102,12 +102,14 @@ class AcceptDeclineInvitePresenter @Inject constructor(
|
||||
|
||||
private fun CoroutineScope.acceptInvite(roomId: RoomId, acceptedAction: MutableState<AsyncAction<RoomId>>) = launch {
|
||||
acceptedAction.runUpdatingState {
|
||||
client.joinRoom(roomId).onSuccess {
|
||||
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId, doRender = true)
|
||||
client.getRoom(roomId)?.use { room ->
|
||||
analyticsService.capture(room.toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite))
|
||||
client.joinRoom(roomId)
|
||||
.onSuccess {
|
||||
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId, doRender = true)
|
||||
client.getRoom(roomId)?.use { room ->
|
||||
analyticsService.capture(room.toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite))
|
||||
}
|
||||
}
|
||||
}
|
||||
.map { roomId }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ class AcceptDeclineInvitePresenterTest {
|
||||
@Test
|
||||
fun `present - accepting invite error flow`() = runTest {
|
||||
val joinRoomFailure = lambdaRecorder { roomId: RoomId ->
|
||||
Result.failure<RoomId>(RuntimeException("Failed to join room $roomId"))
|
||||
Result.failure<Unit>(RuntimeException("Failed to join room $roomId"))
|
||||
}
|
||||
val client = FakeMatrixClient().apply {
|
||||
joinRoomLambda = joinRoomFailure
|
||||
@@ -197,8 +197,8 @@ class AcceptDeclineInvitePresenterTest {
|
||||
|
||||
@Test
|
||||
fun `present - accepting invite success flow`() = runTest {
|
||||
val joinRoomSuccess = lambdaRecorder { roomId: RoomId ->
|
||||
Result.success(roomId)
|
||||
val joinRoomSuccess = lambdaRecorder { _: RoomId ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val client = FakeMatrixClient().apply {
|
||||
joinRoomLambda = joinRoomSuccess
|
||||
|
||||
@@ -19,6 +19,8 @@ package io.element.android.features.joinroom.impl
|
||||
sealed interface JoinRoomEvents {
|
||||
data object RetryFetchingContent : JoinRoomEvents
|
||||
data object JoinRoom : JoinRoomEvents
|
||||
data object KnockRoom : JoinRoomEvents
|
||||
data object ClearError : JoinRoomEvents
|
||||
data object AcceptInvite : JoinRoomEvents
|
||||
data object DeclineInvite : JoinRoomEvents
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ class JoinRoomNode @AssistedInject constructor(
|
||||
JoinRoomView(
|
||||
state = state,
|
||||
onBackPressed = ::navigateUp,
|
||||
onKnockSuccess = ::navigateUp,
|
||||
modifier = modifier
|
||||
)
|
||||
acceptDeclineInviteView.Render(
|
||||
|
||||
@@ -18,19 +18,25 @@ package io.element.android.features.joinroom.impl
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.InviteData
|
||||
import io.element.android.features.joinroom.impl.di.KnockRoom
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
|
||||
@@ -39,6 +45,8 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomInfo
|
||||
import io.element.android.libraries.matrix.api.room.preview.RoomPreview
|
||||
import io.element.android.libraries.matrix.ui.model.toInviteSender
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.Optional
|
||||
|
||||
class JoinRoomPresenter @AssistedInject constructor(
|
||||
@@ -46,6 +54,7 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||
@Assisted private val roomIdOrAlias: RoomIdOrAlias,
|
||||
@Assisted private val roomDescription: Optional<RoomDescription>,
|
||||
private val matrixClient: MatrixClient,
|
||||
private val knockRoom: KnockRoom,
|
||||
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
) : Presenter<JoinRoomState> {
|
||||
interface Factory {
|
||||
@@ -58,8 +67,10 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||
|
||||
@Composable
|
||||
override fun present(): JoinRoomState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var retryCount by remember { mutableIntStateOf(0) }
|
||||
val roomInfo by matrixClient.getRoomInfoFlow(roomId).collectAsState(initial = Optional.empty())
|
||||
val knockAction: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
|
||||
val contentState by produceState<ContentState>(
|
||||
initialValue = ContentState.Loading(roomIdOrAlias),
|
||||
key1 = roomInfo,
|
||||
@@ -101,6 +112,9 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||
AcceptDeclineInviteEvents.AcceptInvite(inviteData)
|
||||
)
|
||||
}
|
||||
JoinRoomEvents.KnockRoom -> {
|
||||
coroutineScope.knockRoom(roomId, knockAction)
|
||||
}
|
||||
JoinRoomEvents.DeclineInvite -> {
|
||||
val inviteData = contentState.toInviteData() ?: return
|
||||
acceptDeclineInviteState.eventSink(
|
||||
@@ -110,15 +124,25 @@ class JoinRoomPresenter @AssistedInject constructor(
|
||||
JoinRoomEvents.RetryFetchingContent -> {
|
||||
retryCount++
|
||||
}
|
||||
JoinRoomEvents.ClearError -> {
|
||||
knockAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return JoinRoomState(
|
||||
contentState = contentState,
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
knockAction = knockAction.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.knockRoom(roomId: RoomId, knockAction: MutableState<AsyncAction<Unit>>) = launch {
|
||||
knockAction.runUpdatingState {
|
||||
knockRoom(roomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun RoomPreview.toContentState(): ContentState {
|
||||
|
||||
@@ -18,6 +18,7 @@ package io.element.android.features.joinroom.impl
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
@@ -29,6 +30,7 @@ import io.element.android.libraries.matrix.ui.model.InviteSender
|
||||
data class JoinRoomState(
|
||||
val contentState: ContentState,
|
||||
val acceptDeclineInviteState: AcceptDeclineInviteState,
|
||||
val knockAction: AsyncAction<Unit>,
|
||||
val eventSink: (JoinRoomEvents) -> Unit
|
||||
) {
|
||||
val joinAuthorisationStatus = when (contentState) {
|
||||
|
||||
@@ -19,6 +19,7 @@ package io.element.android.features.joinroom.impl
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
@@ -105,10 +106,12 @@ fun aLoadedContentState(
|
||||
fun aJoinRoomState(
|
||||
contentState: ContentState = aLoadedContentState(),
|
||||
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
|
||||
knockAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
eventSink: (JoinRoomEvents) -> Unit = {}
|
||||
) = JoinRoomState(
|
||||
contentState = contentState,
|
||||
acceptDeclineInviteState = acceptDeclineInviteState,
|
||||
knockAction = knockAction,
|
||||
eventSink = eventSink
|
||||
)
|
||||
|
||||
|
||||
@@ -16,15 +16,15 @@
|
||||
|
||||
package io.element.android.features.joinroom.impl
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -41,6 +41,7 @@ import io.element.android.libraries.designsystem.atomic.molecules.RoomPreviewMem
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.RoomPreviewOrganism
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.background.LightGradientBackground
|
||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
@@ -60,36 +61,49 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
fun JoinRoomView(
|
||||
state: JoinRoomState,
|
||||
onBackPressed: () -> Unit,
|
||||
onKnockSuccess: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val gradientBackground = remember { LightGradientBackground() }
|
||||
HeaderFooterPage(
|
||||
modifier = modifier.background(gradientBackground),
|
||||
containerColor = Color.Transparent,
|
||||
paddingValues = PaddingValues(16.dp),
|
||||
topBar = {
|
||||
JoinRoomTopBar(onBackClicked = onBackPressed)
|
||||
},
|
||||
content = {
|
||||
JoinRoomContent(contentState = state.contentState)
|
||||
},
|
||||
footer = {
|
||||
JoinRoomFooter(
|
||||
state = state,
|
||||
onAcceptInvite = {
|
||||
state.eventSink(JoinRoomEvents.AcceptInvite)
|
||||
},
|
||||
onDeclineInvite = {
|
||||
state.eventSink(JoinRoomEvents.DeclineInvite)
|
||||
},
|
||||
onJoinRoom = {
|
||||
state.eventSink(JoinRoomEvents.JoinRoom)
|
||||
},
|
||||
onRetry = {
|
||||
state.eventSink(JoinRoomEvents.RetryFetchingContent)
|
||||
}
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
LightGradientBackground()
|
||||
HeaderFooterPage(
|
||||
containerColor = Color.Transparent,
|
||||
paddingValues = PaddingValues(16.dp),
|
||||
topBar = {
|
||||
JoinRoomTopBar(onBackClicked = onBackPressed)
|
||||
},
|
||||
content = {
|
||||
JoinRoomContent(contentState = state.contentState)
|
||||
},
|
||||
footer = {
|
||||
JoinRoomFooter(
|
||||
state = state,
|
||||
onAcceptInvite = {
|
||||
state.eventSink(JoinRoomEvents.AcceptInvite)
|
||||
},
|
||||
onDeclineInvite = {
|
||||
state.eventSink(JoinRoomEvents.DeclineInvite)
|
||||
},
|
||||
onJoinRoom = {
|
||||
state.eventSink(JoinRoomEvents.JoinRoom)
|
||||
},
|
||||
onKnockRoom = {
|
||||
state.eventSink(JoinRoomEvents.KnockRoom)
|
||||
},
|
||||
onRetry = {
|
||||
state.eventSink(JoinRoomEvents.RetryFetchingContent)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
AsyncActionView(
|
||||
async = state.knockAction,
|
||||
onSuccess = { onKnockSuccess() },
|
||||
onErrorDismiss = { state.eventSink(JoinRoomEvents.ClearError) },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -99,6 +113,7 @@ private fun JoinRoomFooter(
|
||||
onAcceptInvite: () -> Unit,
|
||||
onDeclineInvite: () -> Unit,
|
||||
onJoinRoom: () -> Unit,
|
||||
onKnockRoom: () -> Unit,
|
||||
onRetry: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
@@ -142,7 +157,7 @@ private fun JoinRoomFooter(
|
||||
JoinAuthorisationStatus.CanKnock -> {
|
||||
Button(
|
||||
text = stringResource(R.string.screen_join_room_knock_action),
|
||||
onClick = onJoinRoom,
|
||||
onClick = onKnockRoom,
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
size = ButtonSize.Large,
|
||||
)
|
||||
@@ -263,6 +278,7 @@ private fun JoinRoomTopBar(
|
||||
internal fun JoinRoomViewPreview(@PreviewParameter(JoinRoomStateProvider::class) state: JoinRoomState) = ElementPreview {
|
||||
JoinRoomView(
|
||||
state = state,
|
||||
onBackPressed = { }
|
||||
onBackPressed = { },
|
||||
onKnockSuccess = { },
|
||||
)
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ object JoinRoomModule {
|
||||
@Provides
|
||||
fun providesJoinRoomPresenterFactory(
|
||||
client: MatrixClient,
|
||||
knockRoom: KnockRoom,
|
||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
): JoinRoomPresenter.Factory {
|
||||
return object : JoinRoomPresenter.Factory {
|
||||
@@ -48,6 +49,7 @@ object JoinRoomModule {
|
||||
roomIdOrAlias = roomIdOrAlias,
|
||||
roomDescription = roomDescription,
|
||||
matrixClient = client,
|
||||
knockRoom = knockRoom,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.joinroom.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import javax.inject.Inject
|
||||
|
||||
interface KnockRoom {
|
||||
suspend operator fun invoke(roomId: RoomId): Result<Unit>
|
||||
}
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultKnockRoom @Inject constructor(private val client: MatrixClient) : KnockRoom {
|
||||
override suspend fun invoke(roomId: RoomId) = client.knockRoom(roomId)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.joinroom.impl
|
||||
|
||||
import io.element.android.features.joinroom.impl.di.KnockRoom
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
class FakeKnockRoom(
|
||||
var lambda: (RoomId) -> Result<Unit> = { Result.success(Unit) }
|
||||
) : KnockRoom {
|
||||
override suspend fun invoke(roomId: RoomId) = lambda(roomId)
|
||||
}
|
||||
@@ -20,7 +20,9 @@ import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
||||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
||||
import io.element.android.features.joinroom.impl.di.KnockRoom
|
||||
import io.element.android.features.roomdirectory.api.RoomDescription
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
||||
@@ -269,6 +271,38 @@ class JoinRoomPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - emit knock room event`() = runTest {
|
||||
val knockRoomSuccess = lambdaRecorder { _: RoomId ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val knockRoomFailure = lambdaRecorder { roomId: RoomId ->
|
||||
Result.failure<Unit>(RuntimeException("Failed to knock room $roomId"))
|
||||
}
|
||||
val fakeKnockRoom = FakeKnockRoom(knockRoomSuccess)
|
||||
val presenter = createJoinRoomPresenter(knockRoom = fakeKnockRoom)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
awaitItem().also { state ->
|
||||
state.eventSink(JoinRoomEvents.KnockRoom)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.knockAction).isEqualTo(AsyncAction.Success(Unit))
|
||||
fakeKnockRoom.lambda = knockRoomFailure
|
||||
state.eventSink(JoinRoomEvents.KnockRoom)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.knockAction).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
}
|
||||
}
|
||||
assert(knockRoomSuccess)
|
||||
.isCalledOnce()
|
||||
.with(value(A_ROOM_ID))
|
||||
assert(knockRoomFailure)
|
||||
.isCalledOnce()
|
||||
.with(value(A_ROOM_ID))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room is not known RoomPreview is loaded`() = runTest {
|
||||
val client = FakeMatrixClient(
|
||||
@@ -377,6 +411,7 @@ class JoinRoomPresenterTest {
|
||||
roomId: RoomId = A_ROOM_ID,
|
||||
roomDescription: Optional<RoomDescription> = Optional.empty(),
|
||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||
knockRoom: KnockRoom = FakeKnockRoom(),
|
||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() }
|
||||
): JoinRoomPresenter {
|
||||
return JoinRoomPresenter(
|
||||
@@ -384,6 +419,7 @@ class JoinRoomPresenterTest {
|
||||
roomIdOrAlias = roomId.toRoomIdOrAlias(),
|
||||
roomDescription = roomDescription,
|
||||
matrixClient = matrixClient,
|
||||
knockRoom = knockRoom,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter
|
||||
)
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
@@ -72,11 +73,25 @@ class JoinRoomViewTest {
|
||||
),
|
||||
)
|
||||
rule.clickOn(R.string.screen_join_room_knock_action)
|
||||
eventsRecorder.assertSingle(JoinRoomEvents.JoinRoom)
|
||||
eventsRecorder.assertSingle(JoinRoomEvents.KnockRoom)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Accept invitationon IsInvited room emits the expected Event`() {
|
||||
fun `clicking on closing Knock error emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||
rule.setJoinRoomView(
|
||||
aJoinRoomState(
|
||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
|
||||
knockAction = AsyncAction.Failure(Exception("Error")),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_ok)
|
||||
eventsRecorder.assertSingle(JoinRoomEvents.ClearError)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Accept invitation IsInvited room emits the expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||
rule.setJoinRoomView(
|
||||
aJoinRoomState(
|
||||
@@ -118,11 +133,13 @@ class JoinRoomViewTest {
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setJoinRoomView(
|
||||
state: JoinRoomState,
|
||||
onBackPressed: () -> Unit = EnsureNeverCalled(),
|
||||
onKnockSuccess: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
JoinRoomView(
|
||||
state = state,
|
||||
onBackPressed = onBackPressed,
|
||||
onKnockSuccess = onKnockSuccess,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,17 +16,17 @@
|
||||
|
||||
package io.element.android.features.roomaliasresolver.impl
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
@@ -65,23 +65,26 @@ fun RoomAliasResolverView(
|
||||
latestOnAliasResolved(state.resolveState.data)
|
||||
}
|
||||
}
|
||||
val gradientBackground = remember { LightGradientBackground() }
|
||||
HeaderFooterPage(
|
||||
modifier = modifier.background(gradientBackground),
|
||||
containerColor = Color.Transparent,
|
||||
paddingValues = PaddingValues(16.dp),
|
||||
topBar = {
|
||||
RoomAliasResolverTopBar(onBackClicked = onBackPressed)
|
||||
},
|
||||
content = {
|
||||
RoomAliasResolverContent(state = state)
|
||||
},
|
||||
footer = {
|
||||
RoomAliasResolverFooter(
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
)
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
LightGradientBackground()
|
||||
HeaderFooterPage(
|
||||
containerColor = Color.Transparent,
|
||||
paddingValues = PaddingValues(16.dp),
|
||||
topBar = {
|
||||
RoomAliasResolverTopBar(onBackClicked = onBackPressed)
|
||||
},
|
||||
content = {
|
||||
RoomAliasResolverContent(state = state)
|
||||
},
|
||||
footer = {
|
||||
RoomAliasResolverFooter(
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -108,6 +108,7 @@ class RoomDirectoryPresenter @Inject constructor(
|
||||
private fun CoroutineScope.joinRoom(state: MutableState<AsyncAction<RoomId>>, roomId: RoomId) = launch {
|
||||
state.runUpdatingState {
|
||||
joinRoom(roomId)
|
||||
.map { roomId }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import javax.inject.Inject
|
||||
|
||||
interface JoinRoom {
|
||||
suspend operator fun invoke(roomId: RoomId): Result<RoomId>
|
||||
suspend operator fun invoke(roomId: RoomId): Result<Unit>
|
||||
}
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
|
||||
@@ -20,7 +20,7 @@ import io.element.android.features.roomdirectory.impl.root.di.JoinRoom
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
class FakeJoinRoom(
|
||||
var lambda: (RoomId) -> Result<RoomId> = { Result.success(it) }
|
||||
var lambda: (RoomId) -> Result<Unit> = { Result.success(Unit) }
|
||||
) : JoinRoom {
|
||||
override suspend fun invoke(roomId: RoomId) = lambda(roomId)
|
||||
}
|
||||
|
||||
@@ -138,11 +138,11 @@ import org.junit.Test
|
||||
|
||||
@Test
|
||||
fun `present - emit join room event`() = runTest {
|
||||
val joinRoomSuccess = lambdaRecorder { roomId: RoomId ->
|
||||
Result.success(roomId)
|
||||
val joinRoomSuccess = lambdaRecorder { _: RoomId ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val joinRoomFailure = lambdaRecorder { roomId: RoomId ->
|
||||
Result.failure<RoomId>(RuntimeException("Failed to join room $roomId"))
|
||||
Result.failure<Unit>(RuntimeException("Failed to join room $roomId"))
|
||||
}
|
||||
val fakeJoinRoom = FakeJoinRoom(joinRoomSuccess)
|
||||
val presenter = createRoomDirectoryPresenter(joinRoom = fakeJoinRoom)
|
||||
@@ -171,7 +171,7 @@ import org.junit.Test
|
||||
roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(
|
||||
createRoomDirectoryListFactory = { FakeRoomDirectoryList() }
|
||||
),
|
||||
joinRoom: JoinRoom = FakeJoinRoom { Result.success(it) },
|
||||
joinRoom: JoinRoom = FakeJoinRoom { Result.success(Unit) },
|
||||
): RoomDirectoryPresenter {
|
||||
return RoomDirectoryPresenter(
|
||||
dispatchers = testCoroutineDispatchers(),
|
||||
|
||||
@@ -16,46 +16,48 @@
|
||||
|
||||
package io.element.android.libraries.designsystem.background
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.geometry.center
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RadialGradientShader
|
||||
import androidx.compose.ui.graphics.Shader
|
||||
import androidx.compose.ui.graphics.ShaderBrush
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
||||
class LightGradientBackground(
|
||||
private val firstColor: Color = Color(0x1E0DBD8B),
|
||||
private val secondColor: Color = Color(0x001273EB),
|
||||
private val ratio: Float = 642 / 775f,
|
||||
) : ShaderBrush() {
|
||||
override fun createShader(size: Size): Shader {
|
||||
/**
|
||||
* Light gradient background for Join room screens.
|
||||
*/
|
||||
@Composable
|
||||
fun LightGradientBackground(
|
||||
modifier: Modifier = Modifier,
|
||||
backgroundColor: Color = MaterialTheme.colorScheme.background,
|
||||
firstColor: Color = Color(0x1E0DBD8B),
|
||||
secondColor: Color = Color(0x001273EB),
|
||||
ratio: Float = 642 / 775f,
|
||||
) {
|
||||
Canvas(
|
||||
modifier = modifier.fillMaxSize()
|
||||
) {
|
||||
val biggerDimension = size.width * 1.98f
|
||||
return RadialGradientShader(
|
||||
colors = listOf(firstColor, secondColor),
|
||||
center = size.center.copy(x = size.width * ratio, y = size.height * ratio),
|
||||
radius = biggerDimension / 2f,
|
||||
colorStops = listOf(0f, 0.95f)
|
||||
val gradientShaderBrush = ShaderBrush(
|
||||
RadialGradientShader(
|
||||
colors = listOf(firstColor, secondColor),
|
||||
center = size.center.copy(x = size.width * ratio, y = size.height * ratio),
|
||||
radius = biggerDimension / 2f,
|
||||
colorStops = listOf(0f, 0.95f)
|
||||
)
|
||||
)
|
||||
drawRect(backgroundColor, size = size)
|
||||
drawRect(brush = gradientShaderBrush, size = size)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun LightGradientBackgroundPreview() = ElementPreview {
|
||||
val gradientBackground = remember {
|
||||
LightGradientBackground()
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(gradientBackground)
|
||||
)
|
||||
LightGradientBackground()
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.designsystem.components
|
||||
package io.element.android.libraries.designsystem.background
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.Box
|
||||
@@ -31,6 +31,7 @@ import androidx.compose.ui.graphics.LinearGradientShader
|
||||
import androidx.compose.ui.graphics.ShaderBrush
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.components.drawWithLayer
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
||||
@@ -65,7 +65,8 @@ interface MatrixClient : Closeable {
|
||||
suspend fun setDisplayName(displayName: String): Result<Unit>
|
||||
suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit>
|
||||
suspend fun removeAvatar(): Result<Unit>
|
||||
suspend fun joinRoom(roomId: RoomId): Result<RoomId>
|
||||
suspend fun joinRoom(roomId: RoomId): Result<Unit>
|
||||
suspend fun knockRoom(roomId: RoomId): Result<Unit>
|
||||
fun syncService(): SyncService
|
||||
fun sessionVerificationService(): SessionVerificationService
|
||||
fun pushersService(): PushersService
|
||||
|
||||
@@ -440,7 +440,7 @@ class RustMatrixClient(
|
||||
runCatching { client.removeAvatar() }
|
||||
}
|
||||
|
||||
override suspend fun joinRoom(roomId: RoomId): Result<RoomId> = withContext(sessionDispatcher) {
|
||||
override suspend fun joinRoom(roomId: RoomId): Result<Unit> = withContext(sessionDispatcher) {
|
||||
runCatching {
|
||||
client.joinRoomById(roomId.value).destroy()
|
||||
try {
|
||||
@@ -448,10 +448,13 @@ class RustMatrixClient(
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Timeout waiting for the room to be available in the room list")
|
||||
}
|
||||
roomId
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun knockRoom(roomId: RoomId): Result<Unit> {
|
||||
return Result.failure(NotImplementedError("Not yet implemented"))
|
||||
}
|
||||
|
||||
override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> = withContext(sessionDispatcher) {
|
||||
runCatching {
|
||||
client.trackRecentlyVisitedRoom(roomId.value)
|
||||
|
||||
@@ -103,10 +103,12 @@ class FakeMatrixClient(
|
||||
private var setDisplayNameResult: Result<Unit> = Result.success(Unit)
|
||||
private var uploadAvatarResult: Result<Unit> = Result.success(Unit)
|
||||
private var removeAvatarResult: Result<Unit> = Result.success(Unit)
|
||||
var joinRoomLambda: (RoomId) -> Result<RoomId> = {
|
||||
Result.success(it)
|
||||
var joinRoomLambda: (RoomId) -> Result<Unit> = {
|
||||
Result.success(Unit)
|
||||
}
|
||||
var knockRoomLambda: (RoomId) -> Result<Unit> = {
|
||||
Result.success(Unit)
|
||||
}
|
||||
|
||||
var getRoomInfoFlowLambda = { _: RoomId ->
|
||||
flowOf<Optional<MatrixRoomInfo>>(Optional.empty())
|
||||
}
|
||||
@@ -197,7 +199,9 @@ class FakeMatrixClient(
|
||||
return removeAvatarResult
|
||||
}
|
||||
|
||||
override suspend fun joinRoom(roomId: RoomId): Result<RoomId> = joinRoomLambda(roomId)
|
||||
override suspend fun joinRoom(roomId: RoomId): Result<Unit> = joinRoomLambda(roomId)
|
||||
|
||||
override suspend fun knockRoom(roomId: RoomId): Result<Unit> = knockRoomLambda(roomId)
|
||||
|
||||
override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService
|
||||
|
||||
|
||||
Reference in New Issue
Block a user