Room navigation : reuse accept/decline presentation logic

This commit is contained in:
ganfra 2024-04-09 14:16:17 +02:00
parent 5b8690d32e
commit 9ff0e8eccb
23 changed files with 220 additions and 79 deletions

View file

@ -350,6 +350,10 @@ class LoggedInFlowNode @AssistedInject constructor(
backstack.pop() backstack.pop()
} }
override fun onInviteClicked(roomId: RoomId) {
backstack.push(NavTarget.Room(roomId))
}
override fun onInviteAccepted(roomId: RoomId) { override fun onInviteAccepted(roomId: RoomId) {
backstack.push(NavTarget.Room(roomId)) backstack.push(NavTarget.Room(roomId))
} }

View file

@ -24,6 +24,7 @@ import com.bumble.appyx.core.plugin.Plugin
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.invite.api.response.AcceptDeclineInviteView
import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
@ -34,6 +35,7 @@ class JoinRoomNode @AssistedInject constructor(
@Assisted buildContext: BuildContext, @Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>, @Assisted plugins: List<Plugin>,
presenterFactory: JoinRoomPresenter.Factory, presenterFactory: JoinRoomPresenter.Factory,
private val acceptDeclineInviteView: AcceptDeclineInviteView,
) : Node(buildContext, plugins = plugins) { ) : Node(buildContext, plugins = plugins) {
data class Inputs( data class Inputs(
@ -51,5 +53,11 @@ class JoinRoomNode @AssistedInject constructor(
onBackPressed = ::navigateUp, onBackPressed = ::navigateUp,
modifier = modifier modifier = modifier
) )
acceptDeclineInviteView.Render(
state = state.acceptDeclineInviteState,
onInviteAccepted = {},
onInviteDeclined = { navigateUp() },
modifier = Modifier
)
} }
} }

View file

@ -20,16 +20,17 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.rememberCoroutineScope
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter
import io.element.android.features.invite.api.response.InviteData
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.MatrixClient 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.RoomId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import kotlinx.coroutines.launch
import java.util.Optional import java.util.Optional
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
@ -37,6 +38,7 @@ class JoinRoomPresenter @AssistedInject constructor(
@Assisted private val roomId: RoomId, @Assisted private val roomId: RoomId,
private val matrixClient: MatrixClient, private val matrixClient: MatrixClient,
private val roomListService: RoomListService, private val roomListService: RoomListService,
private val acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter,
) : Presenter<JoinRoomState> { ) : Presenter<JoinRoomState> {
interface Factory { interface Factory {
@ -47,6 +49,7 @@ class JoinRoomPresenter @AssistedInject constructor(
override fun present(): JoinRoomState { override fun present(): JoinRoomState {
val userMembership by roomListService.getUserMembershipForRoom(roomId).collectAsState(initial = Optional.empty()) val userMembership by roomListService.getUserMembershipForRoom(roomId).collectAsState(initial = Optional.empty())
val joinAuthorisationStatus = joinAuthorisationStatus(userMembership) val joinAuthorisationStatus = joinAuthorisationStatus(userMembership)
val acceptDeclineInviteState = acceptDeclineInvitePresenter.present()
val roomInfo by produceState<AsyncData<RoomInfo>>(initialValue = AsyncData.Uninitialized, key1 = userMembership) { val roomInfo by produceState<AsyncData<RoomInfo>>(initialValue = AsyncData.Uninitialized, key1 = userMembership) {
value = when { value = when {
userMembership.isPresent -> { userMembership.isPresent -> {
@ -56,6 +59,7 @@ class JoinRoomPresenter @AssistedInject constructor(
roomName = it.displayName, roomName = it.displayName,
roomAlias = it.alias, roomAlias = it.alias,
memberCount = it.activeMemberCount, memberCount = it.activeMemberCount,
isDirect = it.isDirect,
roomAvatarUrl = it.avatarUrl roomAvatarUrl = it.avatarUrl
) )
} }
@ -65,21 +69,17 @@ class JoinRoomPresenter @AssistedInject constructor(
} }
} }
val coroutineScope = rememberCoroutineScope()
fun handleEvents(event: JoinRoomEvents) { fun handleEvents(event: JoinRoomEvents) {
when (event) { when (event) {
JoinRoomEvents.AcceptInvite, JoinRoomEvents.JoinRoom -> { JoinRoomEvents.AcceptInvite, JoinRoomEvents.JoinRoom -> {
coroutineScope.launch { acceptDeclineInviteState.eventSink(
matrixClient.joinRoom(roomId) AcceptDeclineInviteEvents.AcceptInvite(roomInfo.toInviteData())
} )
} }
JoinRoomEvents.DeclineInvite -> { JoinRoomEvents.DeclineInvite -> {
coroutineScope.launch { acceptDeclineInviteState.eventSink(
matrixClient.getRoom(roomId)?.use { AcceptDeclineInviteEvents.DeclineInvite(roomInfo.toInviteData())
it.leave() )
}
}
} }
} }
} }
@ -87,11 +87,21 @@ class JoinRoomPresenter @AssistedInject constructor(
return JoinRoomState( return JoinRoomState(
roomInfo = roomInfo, roomInfo = roomInfo,
joinAuthorisationStatus = joinAuthorisationStatus, joinAuthorisationStatus = joinAuthorisationStatus,
currentAction = CurrentAction.None, acceptDeclineInviteState = acceptDeclineInviteState,
eventSink = ::handleEvents eventSink = ::handleEvents
) )
} }
private fun AsyncData<RoomInfo>.toInviteData(): InviteData {
return dataOrNull().let {
InviteData(
roomId = roomId,
roomName = it?.roomName ?: "",
isDirect = it?.isDirect ?: false
)
}
}
@Composable @Composable
private fun joinAuthorisationStatus(userMembership: Optional<CurrentUserMembership>): JoinAuthorisationStatus { private fun joinAuthorisationStatus(userMembership: Optional<CurrentUserMembership>): JoinAuthorisationStatus {
return when { return when {

View file

@ -17,6 +17,7 @@
package io.element.android.appnav.room.join package io.element.android.appnav.room.join
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
@ -26,7 +27,7 @@ import io.element.android.libraries.matrix.api.core.RoomId
data class JoinRoomState( data class JoinRoomState(
val roomInfo: AsyncData<RoomInfo>, val roomInfo: AsyncData<RoomInfo>,
val joinAuthorisationStatus: JoinAuthorisationStatus, val joinAuthorisationStatus: JoinAuthorisationStatus,
val currentAction: CurrentAction, val acceptDeclineInviteState: AcceptDeclineInviteState,
val eventSink: (JoinRoomEvents) -> Unit val eventSink: (JoinRoomEvents) -> Unit
){ ){
val showMemberCount = roomInfo.dataOrNull()?.memberCount != null val showMemberCount = roomInfo.dataOrNull()?.memberCount != null
@ -37,6 +38,7 @@ data class RoomInfo(
val roomName: String, val roomName: String,
val roomAlias: String?, val roomAlias: String?,
val memberCount: Long?, val memberCount: Long?,
val isDirect: Boolean,
val roomAvatarUrl: String?, val roomAvatarUrl: String?,
) { ) {
fun avatarData(size: AvatarSize): AvatarData { fun avatarData(size: AvatarSize): AvatarData {
@ -55,7 +57,3 @@ enum class JoinAuthorisationStatus {
CanJoin, CanJoin,
Unknown, Unknown,
} }
sealed interface CurrentAction {
data object None : CurrentAction
}

View file

@ -17,6 +17,8 @@
package io.element.android.appnav.room.join package io.element.android.appnav.room.join
import androidx.compose.ui.tooling.preview.PreviewParameterProvider 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.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
@ -45,16 +47,17 @@ fun aJoinRoomState(
roomName = "Element x android", roomName = "Element x android",
roomAlias = "#exa:matrix.org", roomAlias = "#exa:matrix.org",
memberCount = null, memberCount = null,
isDirect = false,
roomAvatarUrl = null roomAvatarUrl = null
) )
), ),
joinAuthorisationStatus: JoinAuthorisationStatus = JoinAuthorisationStatus.Unknown, joinAuthorisationStatus: JoinAuthorisationStatus = JoinAuthorisationStatus.Unknown,
currentAction: CurrentAction = CurrentAction.None, acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
eventSink: (JoinRoomEvents) -> Unit = {} eventSink: (JoinRoomEvents) -> Unit = {}
) = JoinRoomState( ) = JoinRoomState(
roomInfo = roomInfo, roomInfo = roomInfo,
joinAuthorisationStatus = joinAuthorisationStatus, joinAuthorisationStatus = joinAuthorisationStatus,
currentAction = currentAction, acceptDeclineInviteState = acceptDeclineInviteState,
eventSink = eventSink eventSink = eventSink
) )

View file

@ -20,6 +20,7 @@ import com.squareup.anvil.annotations.ContributesTo
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import io.element.android.appnav.room.join.JoinRoomPresenter import io.element.android.appnav.room.join.JoinRoomPresenter
import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient 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.RoomId
@ -32,13 +33,15 @@ object JoinRoomModule {
fun providesJoinRoomPresenterFactory( fun providesJoinRoomPresenterFactory(
roomListService: RoomListService, roomListService: RoomListService,
client: MatrixClient, client: MatrixClient,
acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter,
): JoinRoomPresenter.Factory { ): JoinRoomPresenter.Factory {
return object : JoinRoomPresenter.Factory { return object : JoinRoomPresenter.Factory {
override fun create(roomId: RoomId): JoinRoomPresenter { override fun create(roomId: RoomId): JoinRoomPresenter {
return JoinRoomPresenter( return JoinRoomPresenter(
roomId = roomId, roomId = roomId,
matrixClient = client, matrixClient = client,
roomListService = roomListService roomListService = roomListService,
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
) )
} }
} }

View file

@ -15,7 +15,7 @@
*/ */
plugins { plugins {
id("io.element.android-library") id("io.element.android-compose-library")
} }
android { android {

View file

@ -32,7 +32,7 @@ interface InviteListEntryPoint : FeatureEntryPoint {
interface Callback : Plugin { interface Callback : Plugin {
fun onBackClicked() fun onBackClicked()
fun onInviteClicked(roomId: RoomId)
fun onInviteAccepted(roomId: RoomId) fun onInviteAccepted(roomId: RoomId)
} }
} }

View file

@ -14,13 +14,9 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.invite.impl.response package io.element.android.features.invite.api.response
sealed interface AcceptDeclineInviteEvents { interface AcceptDeclineInviteEvents {
data class AcceptInvite(val invite: InviteData) : AcceptDeclineInviteEvents data class AcceptInvite(val invite: InviteData) : AcceptDeclineInviteEvents
data class DeclineInvite(val invite: InviteData) : AcceptDeclineInviteEvents data class DeclineInvite(val invite: InviteData) : AcceptDeclineInviteEvents
data object ConfirmDeclineInvite : AcceptDeclineInviteEvents
data object CancelDeclineInvite : AcceptDeclineInviteEvents
data object DismissAcceptError : AcceptDeclineInviteEvents
data object DismissDeclineError : AcceptDeclineInviteEvents
} }

View file

@ -0,0 +1,21 @@
/*
* 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.invite.api.response
import io.element.android.libraries.architecture.Presenter
interface AcceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.invite.impl.response package io.element.android.features.invite.api.response
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
@ -24,11 +24,5 @@ data class AcceptDeclineInviteState(
val invite: Optional<InviteData>, val invite: Optional<InviteData>,
val acceptAction: AsyncAction<RoomId>, val acceptAction: AsyncAction<RoomId>,
val declineAction: AsyncAction<RoomId>, val declineAction: AsyncAction<RoomId>,
val eventSink: (AcceptDeclineInviteEvents) -> Unit val eventSink: (AcceptDeclineInviteEvents) -> Unit,
)
data class InviteData(
val roomId: RoomId,
val roomName: String,
val isDirect: Boolean,
) )

View file

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package io.element.android.features.invite.impl.response package io.element.android.features.invite.api.response
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction

View file

@ -0,0 +1,31 @@
/*
* 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.invite.api.response
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import io.element.android.libraries.matrix.api.core.RoomId
interface AcceptDeclineInviteView {
@Composable
fun Render(
state: AcceptDeclineInviteState,
onInviteAccepted: (RoomId) -> Unit,
onInviteDeclined: (RoomId) -> Unit,
modifier: Modifier,
)
}

View file

@ -0,0 +1,25 @@
/*
* 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.invite.api.response
import io.element.android.libraries.matrix.api.core.RoomId
data class InviteData(
val roomId: RoomId,
val roomName: String,
val isDirect: Boolean,
)

View file

@ -43,6 +43,10 @@ class InviteListNode @AssistedInject constructor(
plugins<InviteListEntryPoint.Callback>().forEach { it.onInviteAccepted(roomId) } plugins<InviteListEntryPoint.Callback>().forEach { it.onInviteAccepted(roomId) }
} }
private fun onInviteClicked(roomId: RoomId) {
plugins<InviteListEntryPoint.Callback>().forEach { it.onInviteClicked(roomId) }
}
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
val state = presenter.present() val state = presenter.present()
@ -50,7 +54,8 @@ class InviteListNode @AssistedInject constructor(
state = state, state = state,
onBackClicked = ::onBackClicked, onBackClicked = ::onBackClicked,
onInviteAccepted = ::onInviteAccepted, onInviteAccepted = ::onInviteAccepted,
onInviteDeclined = {} onInviteDeclined = {},
onInviteClicked = ::onInviteClicked,
) )
} }
} }

View file

@ -24,11 +24,11 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.SeenInvitesStore
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter
import io.element.android.features.invite.api.response.InviteData
import io.element.android.features.invite.impl.model.InviteListInviteSummary import io.element.android.features.invite.impl.model.InviteListInviteSummary
import io.element.android.features.invite.impl.model.InviteSender import io.element.android.features.invite.impl.model.InviteSender
import io.element.android.features.invite.impl.response.AcceptDeclineInviteEvents
import io.element.android.features.invite.impl.response.AcceptDeclineInvitePresenter
import io.element.android.features.invite.impl.response.InviteData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize

View file

@ -17,7 +17,7 @@
package io.element.android.features.invite.impl.invitelist package io.element.android.features.invite.impl.invitelist
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import io.element.android.features.invite.impl.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.impl.model.InviteListInviteSummary import io.element.android.features.invite.impl.model.InviteListInviteSummary
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList

View file

@ -17,11 +17,11 @@
package io.element.android.features.invite.impl.invitelist package io.element.android.features.invite.impl.invitelist
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.impl.model.InviteListInviteSummary import io.element.android.features.invite.impl.model.InviteListInviteSummary
import io.element.android.features.invite.impl.model.InviteSender import io.element.android.features.invite.impl.model.InviteSender
import io.element.android.features.invite.impl.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.AcceptDeclineInviteStateProvider
import io.element.android.features.invite.impl.response.AcceptDeclineInviteStateProvider import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
import io.element.android.features.invite.impl.response.anAcceptDeclineInviteState
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList

View file

@ -16,6 +16,7 @@
package io.element.android.features.invite.impl.invitelist package io.element.android.features.invite.impl.invitelist
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets import androidx.compose.foundation.layout.consumeWindowInsets
@ -53,11 +54,13 @@ fun InviteListView(
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onInviteAccepted: (RoomId) -> Unit, onInviteAccepted: (RoomId) -> Unit,
onInviteDeclined: (RoomId) -> Unit, onInviteDeclined: (RoomId) -> Unit,
onInviteClicked: (RoomId) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
InviteListContent( InviteListContent(
state = state, state = state,
modifier = modifier, modifier = modifier,
onInviteClicked = onInviteClicked,
onBackClicked = onBackClicked, onBackClicked = onBackClicked,
) )
AcceptDeclineInviteView( AcceptDeclineInviteView(
@ -72,6 +75,7 @@ fun InviteListView(
private fun InviteListContent( private fun InviteListContent(
state: InviteListState, state: InviteListState,
onBackClicked: () -> Unit, onBackClicked: () -> Unit,
onInviteClicked: (RoomId) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Scaffold( Scaffold(
@ -112,6 +116,9 @@ private fun InviteListContent(
items = state.inviteList, items = state.inviteList,
) { index, invite -> ) { index, invite ->
InviteSummaryRow( InviteSummaryRow(
modifier = Modifier.clickable(
onClick = { onInviteClicked(invite.roomId) }
),
invite = invite, invite = invite,
onAcceptClicked = { state.eventSink(InviteListEvents.AcceptInvite(invite)) }, onAcceptClicked = { state.eventSink(InviteListEvents.AcceptInvite(invite)) },
onDeclineClicked = { state.eventSink(InviteListEvents.DeclineInvite(invite)) }, onDeclineClicked = { state.eventSink(InviteListEvents.DeclineInvite(invite)) },
@ -136,5 +143,6 @@ internal fun InviteListViewPreview(@PreviewParameter(InviteListStateProvider::cl
onBackClicked = {}, onBackClicked = {},
onInviteAccepted = {}, onInviteAccepted = {},
onInviteDeclined = {}, onInviteDeclined = {},
onInviteClicked = {},
) )
} }

View file

@ -22,6 +22,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewLightDark import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.api.response.AcceptDeclineInviteStateProvider
import io.element.android.features.invite.api.response.InviteData
import io.element.android.features.invite.impl.R import io.element.android.features.invite.impl.R
import io.element.android.libraries.designsystem.components.async.AsyncActionView import io.element.android.libraries.designsystem.components.async.AsyncActionView
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
@ -42,14 +45,14 @@ fun AcceptDeclineInviteView(
async = state.acceptAction, async = state.acceptAction,
onSuccess = onInviteAccepted, onSuccess = onInviteAccepted,
onErrorDismiss = { onErrorDismiss = {
state.eventSink(AcceptDeclineInviteEvents.DismissAcceptError) state.eventSink(DefaultAcceptDeclineInviteEvents.DismissAcceptError)
}, },
) )
AsyncActionView( AsyncActionView(
async = state.declineAction, async = state.declineAction,
onSuccess = onInviteDeclined, onSuccess = onInviteDeclined,
onErrorDismiss = { onErrorDismiss = {
state.eventSink(AcceptDeclineInviteEvents.DismissDeclineError) state.eventSink(DefaultAcceptDeclineInviteEvents.DismissDeclineError)
}, },
confirmationDialog = { confirmationDialog = {
val invite = state.invite.getOrNull() val invite = state.invite.getOrNull()
@ -57,10 +60,10 @@ fun AcceptDeclineInviteView(
DeclineConfirmationDialog( DeclineConfirmationDialog(
invite = invite, invite = invite,
onConfirmClicked = { onConfirmClicked = {
state.eventSink(AcceptDeclineInviteEvents.ConfirmDeclineInvite) state.eventSink(DefaultAcceptDeclineInviteEvents.ConfirmDeclineInvite)
}, },
onDismissClicked = { onDismissClicked = {
state.eventSink(AcceptDeclineInviteEvents.CancelDeclineInvite) state.eventSink(DefaultAcceptDeclineInviteEvents.CancelDeclineInvite)
} }
) )
} }

View file

@ -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.invite.impl.response
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
sealed interface DefaultAcceptDeclineInviteEvents: AcceptDeclineInviteEvents {
data object ConfirmDeclineInvite : DefaultAcceptDeclineInviteEvents
data object CancelDeclineInvite : DefaultAcceptDeclineInviteEvents
data object DismissAcceptError : DefaultAcceptDeclineInviteEvents
data object DismissDeclineError : DefaultAcceptDeclineInviteEvents
}

View file

@ -23,10 +23,16 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import com.squareup.anvil.annotations.ContributesBinding
import im.vector.app.features.analytics.plan.JoinedRoom import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import io.element.android.features.invite.api.response.InviteData
import io.element.android.libraries.architecture.AsyncAction 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.architecture.runCatchingUpdatingState
import io.element.android.libraries.architecture.runUpdatingState
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient 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.RoomId
import io.element.android.libraries.push.api.notifications.NotificationDrawerManager import io.element.android.libraries.push.api.notifications.NotificationDrawerManager
@ -38,11 +44,12 @@ import java.util.Optional
import javax.inject.Inject import javax.inject.Inject
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
class AcceptDeclineInvitePresenter @Inject constructor( @ContributesBinding(SessionScope::class)
class DefaultAcceptDeclineInvitePresenter @Inject constructor(
private val client: MatrixClient, private val client: MatrixClient,
private val analyticsService: AnalyticsService, private val analyticsService: AnalyticsService,
private val notificationDrawerManager: NotificationDrawerManager, private val notificationDrawerManager: NotificationDrawerManager,
) : Presenter<AcceptDeclineInviteState> { ) : AcceptDeclineInvitePresenter {
@Composable @Composable
override fun present(): AcceptDeclineInviteState { override fun present(): AcceptDeclineInviteState {
@ -66,7 +73,7 @@ class AcceptDeclineInvitePresenter @Inject constructor(
declinedAction.value = AsyncAction.Confirming declinedAction.value = AsyncAction.Confirming
} }
is AcceptDeclineInviteEvents.ConfirmDeclineInvite -> { is DefaultAcceptDeclineInviteEvents.ConfirmDeclineInvite -> {
declinedAction.value = AsyncAction.Uninitialized declinedAction.value = AsyncAction.Uninitialized
currentInvite.getOrNull()?.let { currentInvite.getOrNull()?.let {
localCoroutineScope.declineInvite(it.roomId, declinedAction) localCoroutineScope.declineInvite(it.roomId, declinedAction)
@ -74,16 +81,16 @@ class AcceptDeclineInvitePresenter @Inject constructor(
currentInvite = Optional.empty() currentInvite = Optional.empty()
} }
is AcceptDeclineInviteEvents.CancelDeclineInvite -> { is DefaultAcceptDeclineInviteEvents.CancelDeclineInvite -> {
currentInvite = Optional.empty() currentInvite = Optional.empty()
declinedAction.value = AsyncAction.Uninitialized declinedAction.value = AsyncAction.Uninitialized
} }
is AcceptDeclineInviteEvents.DismissAcceptError -> { is DefaultAcceptDeclineInviteEvents.DismissAcceptError -> {
acceptedAction.value = AsyncAction.Uninitialized acceptedAction.value = AsyncAction.Uninitialized
} }
is AcceptDeclineInviteEvents.DismissDeclineError -> { is DefaultAcceptDeclineInviteEvents.DismissDeclineError -> {
declinedAction.value = AsyncAction.Uninitialized declinedAction.value = AsyncAction.Uninitialized
} }
} }
@ -98,14 +105,14 @@ class AcceptDeclineInvitePresenter @Inject constructor(
} }
private fun CoroutineScope.acceptInvite(roomId: RoomId, acceptedAction: MutableState<AsyncAction<RoomId>>) = launch { private fun CoroutineScope.acceptInvite(roomId: RoomId, acceptedAction: MutableState<AsyncAction<RoomId>>) = launch {
suspend { acceptedAction.runUpdatingState {
client.getRoom(roomId)?.use { client.joinRoom(roomId).onSuccess {
it.join().getOrThrow()
notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId, doRender = true) notificationDrawerManager.clearMembershipNotificationForRoom(client.sessionId, roomId, doRender = true)
analyticsService.capture(it.toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite)) client.getRoom(roomId)?.use { room ->
analyticsService.capture(room.toAnalyticsJoinedRoom(JoinedRoom.Trigger.Invite))
}
} }
roomId }
}.runCatchingUpdatingState(acceptedAction)
} }
private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState<AsyncAction<RoomId>>) = launch { private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState<AsyncAction<RoomId>>) = launch {

View file

@ -18,28 +18,27 @@ package io.element.android.features.invite.impl.response
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.bumble.appyx.core.modality.BuildContext import com.squareup.anvil.annotations.ContributesBinding
import com.bumble.appyx.core.node.Node import io.element.android.features.invite.api.response.AcceptDeclineInviteState
import com.bumble.appyx.core.plugin.Plugin import io.element.android.features.invite.api.response.AcceptDeclineInviteView
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId
import javax.inject.Inject
@ContributesNode(SessionScope::class) @ContributesBinding(SessionScope::class)
class AcceptDeclineInviteNode @AssistedInject constructor( class DefaultAcceptDeclineInviteView @Inject constructor() : AcceptDeclineInviteView {
@Assisted buildContext: BuildContext,
@Assisted plugins: List<Plugin>,
private val presenter: AcceptDeclineInvitePresenter,
) : Node(buildContext, plugins = plugins) {
@Composable @Composable
override fun View(modifier: Modifier) { override fun Render(
val state = presenter.present() state: AcceptDeclineInviteState,
onInviteAccepted: (RoomId) -> Unit,
onInviteDeclined: (RoomId) -> Unit,
modifier: Modifier,
) {
AcceptDeclineInviteView( AcceptDeclineInviteView(
state = state, state = state,
onInviteAccepted = {}, onInviteAccepted = onInviteAccepted,
onInviteDeclined = {}, onInviteDeclined = onInviteDeclined,
modifier = modifier modifier = modifier
) )
} }