mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 06:55:41 +00:00
Click on avatar in moderation bottom sheet opens avatar preview (#6991)
* Make the avatar in the room member moderation bottom sheet open the avatar in the media viewer (#6962) * Make the avatar in the room member moderation bottom sheet open the avatar in the media viewer. * Fix issue with avatar overlay not dismissing the bottom sheet: the bottom sheet would eat all the touch events until the first click, which would somehow cancel this behaviour * Fix lint issues * Update screenshots --------- Co-authored-by: bxdxnn <267911624+bxdxnn@users.noreply.github.com> Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
committed by
GitHub
parent
786c53ac06
commit
bea2903093
+36
@@ -190,6 +190,9 @@ class MessagesFlowNode(
|
|||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data object ThreadsList : NavTarget
|
data object ThreadsList : NavTarget
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
data class AvatarPreview(val name: String, val avatarUrl: String) : NavTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
private val callback: MessagesEntryPoint.Callback = callback()
|
private val callback: MessagesEntryPoint.Callback = callback()
|
||||||
@@ -327,6 +330,10 @@ class MessagesFlowNode(
|
|||||||
override fun navigateToDeveloperSettings() {
|
override fun navigateToDeveloperSettings() {
|
||||||
callback.navigateToDeveloperSettings()
|
callback.navigateToDeveloperSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun navigateToAvatarPreview(username: String, avatarUrl: String) {
|
||||||
|
overlay.show(NavTarget.AvatarPreview(username, avatarUrl))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val inputs = MessagesNode.Inputs(focusedEventId = navTarget.focusedEventId)
|
val inputs = MessagesNode.Inputs(focusedEventId = navTarget.focusedEventId)
|
||||||
createNode<MessagesNode>(buildContext, listOf(callback, inputs))
|
createNode<MessagesNode>(buildContext, listOf(callback, inputs))
|
||||||
@@ -562,6 +569,10 @@ class MessagesFlowNode(
|
|||||||
override fun navigateToDeveloperSettings() {
|
override fun navigateToDeveloperSettings() {
|
||||||
callback.navigateToDeveloperSettings()
|
callback.navigateToDeveloperSettings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun navigateToAvatarPreview(username: String, avatarUrl: String) {
|
||||||
|
overlay.show(NavTarget.AvatarPreview(username, avatarUrl))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
createNode<ThreadedMessagesNode>(buildContext, listOf(inputs, callback))
|
createNode<ThreadedMessagesNode>(buildContext, listOf(inputs, callback))
|
||||||
}
|
}
|
||||||
@@ -573,6 +584,31 @@ class MessagesFlowNode(
|
|||||||
}
|
}
|
||||||
createNode<ThreadsListNode>(buildContext, listOf(callback))
|
createNode<ThreadsListNode>(buildContext, listOf(callback))
|
||||||
}
|
}
|
||||||
|
is NavTarget.AvatarPreview -> {
|
||||||
|
val callback = object : MediaViewerEntryPoint.Callback {
|
||||||
|
override fun onDone() {
|
||||||
|
overlay.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun viewInTimeline(eventId: EventId) {
|
||||||
|
// Cannot happen
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun forwardEvent(eventId: EventId, fromPinnedEvents: Boolean) {
|
||||||
|
// Cannot happen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val params = mediaViewerEntryPoint.createParamsForAvatar(
|
||||||
|
filename = navTarget.name,
|
||||||
|
avatarUrl = navTarget.avatarUrl,
|
||||||
|
)
|
||||||
|
mediaViewerEntryPoint.createNode(
|
||||||
|
parentNode = this,
|
||||||
|
buildContext = buildContext,
|
||||||
|
params = params,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+8
@@ -66,6 +66,7 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
|
|||||||
import io.element.android.libraries.matrix.api.room.alias.matches
|
import io.element.android.libraries.matrix.api.room.alias.matches
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||||
import io.element.android.libraries.mediaplayer.api.MediaPlayer
|
import io.element.android.libraries.mediaplayer.api.MediaPlayer
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
|
import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
|
||||||
@@ -136,6 +137,8 @@ class MessagesNode(
|
|||||||
fun navigateToDeveloperSettings()
|
fun navigateToDeveloperSettings()
|
||||||
|
|
||||||
fun navigateToThreadsList()
|
fun navigateToThreadsList()
|
||||||
|
|
||||||
|
fun navigateToAvatarPreview(username: String, avatarUrl: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBuilt() {
|
override fun onBuilt() {
|
||||||
@@ -319,6 +322,11 @@ class MessagesNode(
|
|||||||
else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
|
else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onAvatarClick = { user ->
|
||||||
|
user.avatarUrl?.let { url ->
|
||||||
|
callback.navigateToAvatarPreview(user.getBestName(), url)
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+8
@@ -67,6 +67,7 @@ import io.element.android.libraries.matrix.api.room.JoinedRoom
|
|||||||
import io.element.android.libraries.matrix.api.room.alias.matches
|
import io.element.android.libraries.matrix.api.room.alias.matches
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||||
import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
|
import io.element.android.libraries.ui.utils.a11y.hasExternalKeyboard
|
||||||
import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
|
import io.element.android.libraries.ui.utils.a11y.isTalkbackActive
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
@@ -138,6 +139,8 @@ class ThreadedMessagesNode(
|
|||||||
fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean)
|
fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean)
|
||||||
fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?)
|
fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?)
|
||||||
fun navigateToDeveloperSettings()
|
fun navigateToDeveloperSettings()
|
||||||
|
|
||||||
|
fun navigateToAvatarPreview(username: String, avatarUrl: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBuilt() {
|
override fun onBuilt() {
|
||||||
@@ -315,6 +318,11 @@ class ThreadedMessagesNode(
|
|||||||
else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
|
else -> state.roomMemberModerationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onAvatarClick = { user ->
|
||||||
|
user.avatarUrl?.let { url ->
|
||||||
|
callback.navigateToAvatarPreview(user.getBestName(), url)
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+4
@@ -258,6 +258,10 @@ class RoomDetailsFlowNode(
|
|||||||
override fun navigateToInviteMembers() {
|
override fun navigateToInviteMembers() {
|
||||||
backstack.push(NavTarget.InviteMembers)
|
backstack.push(NavTarget.InviteMembers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun navigateToAvatarPreview(username: String, avatarUrl: String) {
|
||||||
|
overlay.show(NavTarget.AvatarPreview(username, avatarUrl))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
createNode<RoomMemberListNode>(buildContext, listOf(roomMemberListCallback))
|
createNode<RoomMemberListNode>(buildContext, listOf(roomMemberListCallback))
|
||||||
}
|
}
|
||||||
|
|||||||
+7
@@ -27,6 +27,7 @@ import io.element.android.libraries.architecture.appyx.launchMolecule
|
|||||||
import io.element.android.libraries.architecture.callback
|
import io.element.android.libraries.architecture.callback
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
import io.element.android.libraries.matrix.ui.model.getBestName
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
|
||||||
@ContributesNode(RoomScope::class)
|
@ContributesNode(RoomScope::class)
|
||||||
@@ -41,6 +42,7 @@ class RoomMemberListNode(
|
|||||||
interface Callback : Plugin {
|
interface Callback : Plugin {
|
||||||
fun navigateToRoomMemberDetails(roomMemberId: UserId)
|
fun navigateToRoomMemberDetails(roomMemberId: UserId)
|
||||||
fun navigateToInviteMembers()
|
fun navigateToInviteMembers()
|
||||||
|
fun navigateToAvatarPreview(username: String, avatarUrl: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val callback: Callback = callback()
|
private val callback: Callback = callback()
|
||||||
@@ -82,6 +84,11 @@ class RoomMemberListNode(
|
|||||||
else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
|
else -> state.moderationState.eventSink(RoomMemberModerationEvents.ProcessAction(action, target))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onAvatarClick = { user ->
|
||||||
|
user.avatarUrl?.let { url ->
|
||||||
|
callback.navigateToAvatarPreview(user.getBestName(), url)
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -17,6 +17,7 @@ interface RoomMemberModerationRenderer {
|
|||||||
fun Render(
|
fun Render(
|
||||||
state: RoomMemberModerationState,
|
state: RoomMemberModerationState,
|
||||||
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
||||||
|
onAvatarClick: ((MatrixUser) -> Unit)?,
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -25,10 +25,11 @@ class DefaultRoomMemberModerationRenderer : RoomMemberModerationRenderer {
|
|||||||
override fun Render(
|
override fun Render(
|
||||||
state: RoomMemberModerationState,
|
state: RoomMemberModerationState,
|
||||||
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
||||||
|
onAvatarClick: ((MatrixUser) -> Unit)?,
|
||||||
modifier: Modifier
|
modifier: Modifier
|
||||||
) {
|
) {
|
||||||
if (state is InternalRoomMemberModerationState) {
|
if (state is InternalRoomMemberModerationState) {
|
||||||
RoomMemberModerationView(state, onSelectAction, modifier)
|
RoomMemberModerationView(modifier = modifier, state = state, onSelectAction = onSelectAction, onAvatarClick = onAvatarClick)
|
||||||
} else {
|
} else {
|
||||||
SideEffect {
|
SideEffect {
|
||||||
Timber.d("RoomMemberModerationRenderer: Render called with unsupported state: $state")
|
Timber.d("RoomMemberModerationRenderer: Render called with unsupported state: $state")
|
||||||
|
|||||||
+17
-10
@@ -12,6 +12,7 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
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.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.heightIn
|
import androidx.compose.foundation.layout.heightIn
|
||||||
@@ -64,6 +65,7 @@ import timber.log.Timber
|
|||||||
fun RoomMemberModerationView(
|
fun RoomMemberModerationView(
|
||||||
state: InternalRoomMemberModerationState,
|
state: InternalRoomMemberModerationState,
|
||||||
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
||||||
|
onAvatarClick: ((MatrixUser) -> Unit)?,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
@@ -73,6 +75,7 @@ fun RoomMemberModerationView(
|
|||||||
user = selectedUser,
|
user = selectedUser,
|
||||||
actions = state.actions,
|
actions = state.actions,
|
||||||
onSelectAction = onSelectAction,
|
onSelectAction = onSelectAction,
|
||||||
|
onAvatarClick = onAvatarClick,
|
||||||
onDismiss = { state.eventSink(InternalRoomMemberModerationEvents.Reset) },
|
onDismiss = { state.eventSink(InternalRoomMemberModerationEvents.Reset) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -214,6 +217,7 @@ private fun RoomMemberActionsBottomSheet(
|
|||||||
user: MatrixUser,
|
user: MatrixUser,
|
||||||
actions: ImmutableList<ModerationActionState>,
|
actions: ImmutableList<ModerationActionState>,
|
||||||
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
onSelectAction: (ModerationAction, MatrixUser) -> Unit,
|
||||||
|
onAvatarClick: ((MatrixUser) -> Unit)? = null,
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
@@ -240,10 +244,11 @@ private fun RoomMemberActionsBottomSheet(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = 24.dp)
|
.padding(bottom = 24.dp)
|
||||||
.align(Alignment.CenterHorizontally)
|
.align(Alignment.CenterHorizontally)
|
||||||
.clickable {
|
.clickable(enabled = user.avatarUrl != null && onAvatarClick != null) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
onSelectAction(ModerationAction.DisplayProfile, user)
|
|
||||||
bottomSheetState.hide()
|
bottomSheetState.hide()
|
||||||
|
onAvatarClick?.invoke(user)
|
||||||
|
onDismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -282,8 +287,8 @@ private fun RoomMemberActionsBottomSheet(
|
|||||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.UserProfile())),
|
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.UserProfile())),
|
||||||
onClick = {
|
onClick = {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
onSelectAction(action, user)
|
|
||||||
bottomSheetState.hide()
|
bottomSheetState.hide()
|
||||||
|
onSelectAction(action, user)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
enabled = actionState.isEnabled
|
enabled = actionState.isEnabled
|
||||||
@@ -340,16 +345,18 @@ private fun RoomMemberActionsBottomSheet(
|
|||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun RoomMemberModerationViewPreview(@PreviewParameter(InternalRoomMemberModerationStateProvider::class) state: InternalRoomMemberModerationState) {
|
internal fun RoomMemberModerationViewPreview(@PreviewParameter(InternalRoomMemberModerationStateProvider::class) state: InternalRoomMemberModerationState) {
|
||||||
|
val isDoingAction = listOf(state.kickUserAsyncAction, state.banUserAsyncAction, state.unbanUserAsyncAction).any { it is AsyncAction.Loading }
|
||||||
|
val modifier = if (isDoingAction) {
|
||||||
|
Modifier.fillMaxWidth().heightIn(min = 64.dp)
|
||||||
|
} else {
|
||||||
|
Modifier.fillMaxSize()
|
||||||
|
}
|
||||||
ElementPreview {
|
ElementPreview {
|
||||||
Box(
|
Box(modifier) {
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.heightIn(min = 64.dp)
|
|
||||||
) {
|
|
||||||
RoomMemberModerationView(
|
RoomMemberModerationView(
|
||||||
state = state,
|
state = state,
|
||||||
onSelectAction = { _, _ ->
|
onSelectAction = { _, _ -> },
|
||||||
},
|
onAvatarClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user