Browse Source

Branch StartDM in the RoomMemberDetails screen

pull/1938/head
ganfra 10 months ago
parent
commit
e55fab29e4
  1. 4
      appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt
  2. 5
      appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt
  3. 2
      features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt
  4. 1
      features/roomdetails/impl/build.gradle.kts
  5. 5
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt
  6. 1
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt
  7. 4
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt
  8. 2
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt
  9. 17
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt
  10. 18
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt
  11. 2
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt
  12. 1
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt
  13. 47
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt

4
appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt

@ -256,6 +256,10 @@ class LoggedInFlowNode @AssistedInject constructor( @@ -256,6 +256,10 @@ class LoggedInFlowNode @AssistedInject constructor(
}
is NavTarget.Room -> {
val callback = object : RoomLoadedFlowNode.Callback {
override fun onOpenRoom(roomId: RoomId) {
backstack.push(NavTarget.Room(roomId))
}
override fun onForwardedToSingleRoom(roomId: RoomId) {
coroutineScope.launch { attachRoom(roomId) }
}

5
appnav/src/main/kotlin/io/element/android/appnav/room/RoomLoadedFlowNode.kt

@ -74,6 +74,7 @@ class RoomLoadedFlowNode @AssistedInject constructor( @@ -74,6 +74,7 @@ class RoomLoadedFlowNode @AssistedInject constructor(
), DaggerComponentOwner {
interface Callback : Plugin {
fun onOpenRoom(roomId: RoomId)
fun onForwardedToSingleRoom(roomId: RoomId)
fun onOpenGlobalNotificationSettings()
}
@ -134,6 +135,10 @@ class RoomLoadedFlowNode @AssistedInject constructor( @@ -134,6 +135,10 @@ class RoomLoadedFlowNode @AssistedInject constructor(
override fun onOpenGlobalNotificationSettings() {
callbacks.forEach { it.onOpenGlobalNotificationSettings() }
}
override fun onOpenRoom(roomId: RoomId) {
callbacks.forEach { it.onOpenRoom(roomId) }
}
}
return roomDetailsEntryPoint.nodeBuilder(this, buildContext)
.params(RoomDetailsEntryPoint.Params(initialTarget))

2
features/roomdetails/api/src/main/kotlin/io/element/android/features/roomdetails/api/RoomDetailsEntryPoint.kt

@ -22,6 +22,7 @@ import com.bumble.appyx.core.node.Node @@ -22,6 +22,7 @@ import com.bumble.appyx.core.node.Node
import com.bumble.appyx.core.plugin.Plugin
import io.element.android.libraries.architecture.FeatureEntryPoint
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import kotlinx.parcelize.Parcelize
@ -42,6 +43,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint { @@ -42,6 +43,7 @@ interface RoomDetailsEntryPoint : FeatureEntryPoint {
interface Callback : Plugin {
fun onOpenGlobalNotificationSettings()
fun onOpenRoom(roomId: RoomId)
}
interface NodeBuilder {

1
features/roomdetails/impl/build.gradle.kts

@ -51,6 +51,7 @@ dependencies { @@ -51,6 +51,7 @@ dependencies {
api(projects.services.apperror.api)
implementation(libs.coil.compose)
implementation(projects.features.leaveroom.api)
implementation(projects.features.createroom.api)
implementation(projects.services.analytics.api)
testImplementation(libs.test.junit)

5
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt

@ -41,6 +41,7 @@ import io.element.android.libraries.architecture.animation.rememberDefaultTransi @@ -41,6 +41,7 @@ import io.element.android.libraries.architecture.animation.rememberDefaultTransi
import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.di.RoomScope
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.media.MediaSource
import io.element.android.libraries.mediaviewer.api.local.MediaInfo
@ -152,6 +153,10 @@ class RoomDetailsFlowNode @AssistedInject constructor( @@ -152,6 +153,10 @@ class RoomDetailsFlowNode @AssistedInject constructor(
override fun openAvatarPreview(username: String, avatarUrl: String) {
backstack.push(NavTarget.MemberAvatarPreview(username, avatarUrl))
}
override fun onStartDM(roomId: RoomId) {
plugins<RoomDetailsEntryPoint.Callback>().forEach { it.onOpenRoom(roomId) }
}
}
val plugins = listOf(RoomMemberDetailsNode.RoomMemberDetailsInput(navTarget.roomMemberId), callback)
createNode<RoomMemberDetailsNode>(buildContext, plugins)

1
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/blockuser/BlockUserSection.kt

@ -85,6 +85,7 @@ private fun PreferenceBlockUser( @@ -85,6 +85,7 @@ private fun PreferenceBlockUser(
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block)),
onClick = { if (!isLoading) eventSink(RoomMemberDetailsEvents.UnblockUser(needsConfirmation = true)) },
trailingContent = if (isLoading) ListItemContent.Custom(loadingCurrentValue) else null,
style = ListItemStyle.Primary,
modifier = modifier,
)
} else {

4
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt

@ -19,6 +19,7 @@ package io.element.android.features.roomdetails.impl.di @@ -19,6 +19,7 @@ package io.element.android.features.roomdetails.impl.di
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.Provides
import io.element.android.features.createroom.api.StartDMAction
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.MatrixClient
@ -33,10 +34,11 @@ object RoomMemberModule { @@ -33,10 +34,11 @@ object RoomMemberModule {
fun provideRoomMemberDetailsPresenterFactory(
matrixClient: MatrixClient,
room: MatrixRoom,
startDMAction: StartDMAction,
): RoomMemberDetailsPresenter.Factory {
return object : RoomMemberDetailsPresenter.Factory {
override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter {
return RoomMemberDetailsPresenter(matrixClient, room, roomMemberId)
return RoomMemberDetailsPresenter(roomMemberId, matrixClient, room, startDMAction)
}
}
}

2
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsEvents.kt

@ -17,6 +17,8 @@ @@ -17,6 +17,8 @@
package io.element.android.features.roomdetails.impl.members.details
sealed interface RoomMemberDetailsEvents {
data object StartDM : RoomMemberDetailsEvents
data object ClearStartDMState : RoomMemberDetailsEvents
data class BlockUser(val needsConfirmation: Boolean = false) : RoomMemberDetailsEvents
data class UnblockUser(val needsConfirmation: Boolean = false) : RoomMemberDetailsEvents
data object ClearBlockUserError : RoomMemberDetailsEvents

17
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsNode.kt

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
package io.element.android.features.roomdetails.impl.members.details
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import com.bumble.appyx.core.lifecycle.subscribe
@ -29,9 +30,11 @@ import im.vector.app.features.analytics.plan.MobileScreen @@ -29,9 +30,11 @@ import im.vector.app.features.analytics.plan.MobileScreen
import io.element.android.anvilannotations.ContributesNode
import io.element.android.features.roomdetails.impl.R
import io.element.android.libraries.androidutils.system.startSharePlainTextIntent
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.di.RoomScope
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.permalink.PermalinkBuilder
import io.element.android.services.analytics.api.AnalyticsService
@ -46,8 +49,9 @@ class RoomMemberDetailsNode @AssistedInject constructor( @@ -46,8 +49,9 @@ class RoomMemberDetailsNode @AssistedInject constructor(
presenterFactory: RoomMemberDetailsPresenter.Factory,
) : Node(buildContext, plugins = plugins) {
interface Callback: NodeInputs {
interface Callback : NodeInputs {
fun openAvatarPreview(username: String, avatarUrl: String)
fun onStartDM(roomId: RoomId)
}
data class RoomMemberDetailsInput(
@ -84,12 +88,23 @@ class RoomMemberDetailsNode @AssistedInject constructor( @@ -84,12 +88,23 @@ class RoomMemberDetailsNode @AssistedInject constructor(
}
}
fun onStartDM(roomId: RoomId) {
callback.onStartDM(roomId)
}
val state = presenter.present()
LaunchedEffect(state.startDmActionState) {
if (state.startDmActionState is Async.Success) {
onStartDM(state.startDmActionState.data)
}
}
RoomMemberDetailsView(
state = state,
modifier = modifier,
goBack = this::navigateUp,
onShareUser = ::onShareUser,
onDMStarted = ::onStartDM,
openAvatarPreview = callback::openAvatarPreview,
)
}

18
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt

@ -25,13 +25,18 @@ import androidx.compose.runtime.produceState @@ -25,13 +25,18 @@ import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import com.squareup.anvil.annotations.ContributesBinding
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import io.element.android.features.createroom.api.StartDMAction
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.di.RoomScope
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.UserId
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.ui.room.getRoomMemberAsState
@ -39,9 +44,10 @@ import kotlinx.coroutines.CoroutineScope @@ -39,9 +44,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
class RoomMemberDetailsPresenter @AssistedInject constructor(
@Assisted private val roomMemberId: UserId,
private val client: MatrixClient,
private val room: MatrixRoom,
@Assisted private val roomMemberId: UserId,
private val startDMAction: StartDMAction,
) : Presenter<RoomMemberDetailsState> {
interface Factory {
@ -53,6 +59,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( @@ -53,6 +59,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
val coroutineScope = rememberCoroutineScope()
var confirmationDialog by remember { mutableStateOf<ConfirmationDialog?>(null) }
val roomMember by room.getRoomMemberAsState(roomMemberId)
val startDmActionState: MutableState<Async<RoomId>> = remember { mutableStateOf(Async.Uninitialized) }
// the room member is not really live...
val isBlocked: MutableState<Async<Boolean>> = remember(roomMember) {
val isIgnored = roomMember?.isIgnored
@ -88,6 +95,14 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( @@ -88,6 +95,14 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
RoomMemberDetailsEvents.ClearBlockUserError -> {
isBlocked.value = Async.Success(isBlocked.value.dataOrNull().orFalse())
}
RoomMemberDetailsEvents.StartDM -> {
coroutineScope.launch {
startDMAction.execute(roomMemberId, startDmActionState)
}
}
RoomMemberDetailsEvents.ClearStartDMState -> {
startDmActionState.value = Async.Uninitialized
}
}
}
@ -108,6 +123,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor( @@ -108,6 +123,7 @@ class RoomMemberDetailsPresenter @AssistedInject constructor(
userName = userName,
avatarUrl = userAvatar,
isBlocked = isBlocked.value,
startDmActionState = startDmActionState.value,
displayConfirmationDialog = confirmationDialog,
isCurrentUser = client.isMe(roomMember?.userId),
eventSink = ::handleEvents

2
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsState.kt

@ -17,12 +17,14 @@ @@ -17,12 +17,14 @@
package io.element.android.features.roomdetails.impl.members.details
import io.element.android.libraries.architecture.Async
import io.element.android.libraries.matrix.api.core.RoomId
data class RoomMemberDetailsState(
val userId: String,
val userName: String?,
val avatarUrl: String?,
val isBlocked: Async<Boolean>,
val startDmActionState: Async<RoomId>,
val displayConfirmationDialog: ConfirmationDialog?,
val isCurrentUser: Boolean,
val eventSink: (RoomMemberDetailsEvents) -> Unit

1
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt

@ -37,6 +37,7 @@ fun aRoomMemberDetailsState() = RoomMemberDetailsState( @@ -37,6 +37,7 @@ fun aRoomMemberDetailsState() = RoomMemberDetailsState(
userName = "Daniel",
avatarUrl = null,
isBlocked = Async.Success(false),
startDmActionState = Async.Uninitialized,
displayConfirmationDialog = null,
isCurrentUser = false,
eventSink = {},

47
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsView.kt

@ -26,22 +26,33 @@ import androidx.compose.foundation.verticalScroll @@ -26,22 +26,33 @@ 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
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.roomdetails.impl.blockuser.BlockUserDialogs
import io.element.android.features.roomdetails.impl.blockuser.BlockUserSection
import io.element.android.libraries.designsystem.components.async.AsyncView
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.components.list.ListItemContent
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.ListItemStyle
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.ui.strings.CommonStrings
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RoomMemberDetailsView(
state: RoomMemberDetailsState,
onShareUser: () -> Unit,
onDMStarted: (RoomId) -> Unit,
goBack: () -> Unit,
openAvatarPreview: (username: String, url: String) -> Unit,
modifier: Modifier = Modifier,
@ -71,31 +82,36 @@ fun RoomMemberDetailsView( @@ -71,31 +82,36 @@ fun RoomMemberDetailsView(
Spacer(modifier = Modifier.height(26.dp))
// TODO implement send DM
// SendMessageSection(onSendMessage = {
// ...
// })
if (!state.isCurrentUser) {
StartDMSection(onStartDMClicked = { state.eventSink(RoomMemberDetailsEvents.StartDM) })
BlockUserSection(state)
BlockUserDialogs(state)
}
AsyncView(
async = state.startDmActionState,
progressText = stringResource(CommonStrings.common_starting_chat),
onSuccess = onDMStarted,
errorMessage = { stringResource(CommonStrings.common_error) },
onRetry = { state.eventSink(RoomMemberDetailsEvents.StartDM) },
onErrorDismiss = { state.eventSink(RoomMemberDetailsEvents.ClearStartDMState) },
)
}
}
}
/*
@Composable
private fun SendMessageSection(onSendMessage: () -> Unit, modifier: Modifier = Modifier) {
PreferenceCategory(modifier = modifier) {
PreferenceText(
title = stringResource(CommonStrings.action_send_message),
icon = Icons.Outlined.ChatBubbleOutline,
onClick = onSendMessage,
)
}
private fun StartDMSection(
onStartDMClicked: () -> Unit,
modifier: Modifier = Modifier
) {
ListItem(
headlineContent = { Text(stringResource(CommonStrings.common_direct_chat)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Chat)),
style = ListItemStyle.Primary,
onClick = onStartDMClicked,
modifier = modifier,
)
}
*/
@PreviewWithLargeHeight
@Composable
@ -113,6 +129,7 @@ private fun ContentToPreview(state: RoomMemberDetailsState) { @@ -113,6 +129,7 @@ private fun ContentToPreview(state: RoomMemberDetailsState) {
state = state,
onShareUser = {},
goBack = {},
onDMStarted = {},
openAvatarPreview = { _, _ -> }
)
}

Loading…
Cancel
Save