Open room member avatar in a media viewer (#1911)
* Open room member avatar in viewer. The `MediaViewer` was extracted to its own library module. * Update screenshots * Restore KSP processor in `:libraries:mediaviewer:api`, this should generate Showkase components again. --------- Co-authored-by: ElementBot <benoitm+elementbot@element.io>
This commit is contained in:
parent
d0cc8c0b2f
commit
bbc4d18a9d
68 changed files with 439 additions and 122 deletions
|
|
@ -34,12 +34,17 @@ import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode
|
|||
import io.element.android.features.roomdetails.impl.invite.RoomInviteMembersNode
|
||||
import io.element.android.features.roomdetails.impl.members.RoomMemberListNode
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsNode
|
||||
import io.element.android.features.roomdetails.impl.members.details.avatar.RoomMemberAvatarPreviewNode
|
||||
import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsNode
|
||||
import io.element.android.libraries.architecture.BackstackNode
|
||||
import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler
|
||||
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.UserId
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.mediaviewer.api.local.MediaInfo
|
||||
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ContributesNode(RoomScope::class)
|
||||
|
|
@ -79,6 +84,9 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
|||
|
||||
@Parcelize
|
||||
data class RoomMemberDetails(val roomMemberId: UserId) : NavTarget
|
||||
|
||||
@Parcelize
|
||||
data class MemberAvatarPreview(val userName: String, val avatarUrl: String) : NavTarget
|
||||
}
|
||||
|
||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||
|
|
@ -100,6 +108,10 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
|||
override fun openRoomNotificationSettings() {
|
||||
backstack.push(NavTarget.RoomNotificationSettings(showUserDefinedSettingStyle = false))
|
||||
}
|
||||
|
||||
override fun openAvatarPreview(username: String, url: String) {
|
||||
backstack.push(NavTarget.MemberAvatarPreview(username, url))
|
||||
}
|
||||
}
|
||||
createNode<RoomDetailsNode>(buildContext, listOf(roomDetailsCallback))
|
||||
}
|
||||
|
|
@ -136,9 +148,31 @@ class RoomDetailsFlowNode @AssistedInject constructor(
|
|||
}
|
||||
|
||||
is NavTarget.RoomMemberDetails -> {
|
||||
val plugins = listOf(RoomMemberDetailsNode.RoomMemberDetailsInput(navTarget.roomMemberId))
|
||||
val callback = object : RoomMemberDetailsNode.Callback {
|
||||
override fun openAvatarPreview(username: String, avatarUrl: String) {
|
||||
backstack.push(NavTarget.MemberAvatarPreview(username, avatarUrl))
|
||||
}
|
||||
}
|
||||
val plugins = listOf(RoomMemberDetailsNode.RoomMemberDetailsInput(navTarget.roomMemberId), callback)
|
||||
createNode<RoomMemberDetailsNode>(buildContext, plugins)
|
||||
}
|
||||
is NavTarget.MemberAvatarPreview -> {
|
||||
// We need to fake the MimeType here for the viewer to work.
|
||||
val mimeType = MimeTypes.Images
|
||||
val input = MediaViewerNode.Inputs(
|
||||
mediaInfo = MediaInfo(
|
||||
name = navTarget.userName,
|
||||
mimeType = mimeType,
|
||||
formattedFileSize = "",
|
||||
fileExtension = ""
|
||||
),
|
||||
mediaSource = MediaSource(url = navTarget.avatarUrl),
|
||||
thumbnailSource = MediaSource(url = navTarget.avatarUrl),
|
||||
canDownload = false,
|
||||
canShare = false,
|
||||
)
|
||||
createNode<RoomMemberAvatarPreviewNode>(buildContext, listOf(input))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ class RoomDetailsNode @AssistedInject constructor(
|
|||
fun openInviteMembers()
|
||||
fun editRoomDetails()
|
||||
fun openRoomNotificationSettings()
|
||||
fun openAvatarPreview(username: String, url: String)
|
||||
}
|
||||
|
||||
private val callbacks = plugins<Callback>()
|
||||
|
|
@ -110,6 +111,10 @@ class RoomDetailsNode @AssistedInject constructor(
|
|||
callbacks.forEach { it.editRoomDetails() }
|
||||
}
|
||||
|
||||
private fun openAvatarPreview(username: String, url: String) {
|
||||
callbacks.forEach { it.openAvatarPreview(username, url) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val context = LocalContext.current
|
||||
|
|
@ -140,6 +145,7 @@ class RoomDetailsNode @AssistedInject constructor(
|
|||
openRoomMemberList = ::openRoomMemberList,
|
||||
openRoomNotificationSettings = ::openRoomNotificationSettings,
|
||||
invitePeople = ::invitePeople,
|
||||
openAvatarPreview = ::openAvatarPreview,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ fun RoomDetailsView(
|
|||
openRoomMemberList: () -> Unit,
|
||||
openRoomNotificationSettings: () -> Unit,
|
||||
invitePeople: () -> Unit,
|
||||
openAvatarPreview: (username: String, url: String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun onShareMember() {
|
||||
|
|
@ -132,7 +133,12 @@ fun RoomDetailsView(
|
|||
RoomMemberHeaderSection(
|
||||
avatarUrl = state.roomAvatarUrl ?: member.avatarUrl,
|
||||
userId = member.userId.value,
|
||||
userName = state.roomName
|
||||
userName = state.roomName,
|
||||
openAvatarPreview = {
|
||||
if (member.avatarUrl != null) {
|
||||
openAvatarPreview(member.displayName ?: member.userId.value, member.avatarUrl!!)
|
||||
}
|
||||
},
|
||||
)
|
||||
RoomMemberMainActionsSection(onShareUser = ::onShareMember)
|
||||
}
|
||||
|
|
@ -411,5 +417,6 @@ private fun ContentToPreview(state: RoomDetailsState) {
|
|||
openRoomMemberList = {},
|
||||
openRoomNotificationSettings = {},
|
||||
invitePeople = {},
|
||||
openAvatarPreview = { _, _ -> },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,11 +46,16 @@ class RoomMemberDetailsNode @AssistedInject constructor(
|
|||
presenterFactory: RoomMemberDetailsPresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
interface Callback: NodeInputs {
|
||||
fun openAvatarPreview(username: String, avatarUrl: String)
|
||||
}
|
||||
|
||||
data class RoomMemberDetailsInput(
|
||||
val roomMemberId: UserId
|
||||
) : NodeInputs
|
||||
|
||||
private val inputs = inputs<RoomMemberDetailsInput>()
|
||||
private val callback = inputs<Callback>()
|
||||
private val presenter = presenterFactory.create(inputs.roomMemberId)
|
||||
|
||||
init {
|
||||
|
|
@ -84,7 +89,8 @@ class RoomMemberDetailsNode @AssistedInject constructor(
|
|||
state = state,
|
||||
modifier = modifier,
|
||||
goBack = this::navigateUp,
|
||||
onShareUser = ::onShareUser
|
||||
onShareUser = ::onShareUser,
|
||||
openAvatarPreview = callback::openAvatarPreview,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@
|
|||
package io.element.android.features.roomdetails.impl.members.details
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
|
@ -38,12 +37,13 @@ import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
|
|||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
import io.element.android.libraries.designsystem.theme.components.TopAppBar
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun RoomMemberDetailsView(
|
||||
state: RoomMemberDetailsState,
|
||||
onShareUser: () -> Unit,
|
||||
goBack: () -> Unit,
|
||||
openAvatarPreview: (username: String, url: String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Scaffold(
|
||||
|
|
@ -62,6 +62,9 @@ fun RoomMemberDetailsView(
|
|||
avatarUrl = state.avatarUrl,
|
||||
userId = state.userId,
|
||||
userName = state.userName,
|
||||
openAvatarPreview = { avatarUrl ->
|
||||
openAvatarPreview(state.userName ?: state.userId, avatarUrl)
|
||||
},
|
||||
)
|
||||
|
||||
RoomMemberMainActionsSection(onShareUser = onShareUser)
|
||||
|
|
@ -110,5 +113,6 @@ private fun ContentToPreview(state: RoomMemberDetailsState) {
|
|||
state = state,
|
||||
onShareUser = {},
|
||||
goBack = {},
|
||||
openAvatarPreview = { _, _ -> }
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.element.android.features.roomdetails.impl.members.details
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
|
|
@ -41,13 +42,16 @@ fun RoomMemberHeaderSection(
|
|||
avatarUrl: String?,
|
||||
userId: String,
|
||||
userName: String?,
|
||||
openAvatarPreview: (url: String) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Column(modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Box(modifier = Modifier.size(70.dp)) {
|
||||
Avatar(
|
||||
avatarData = AvatarData(userId, userName, avatarUrl, AvatarSize.UserHeader),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
modifier = Modifier
|
||||
.clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) }
|
||||
.fillMaxSize()
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2023 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.roomdetails.impl.members.details.avatar
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode
|
||||
import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerPresenter
|
||||
|
||||
@ContributesNode(RoomScope::class)
|
||||
class RoomMemberAvatarPreviewNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
presenterFactory: MediaViewerPresenter.Factory,
|
||||
) : MediaViewerNode(buildContext, plugins, presenterFactory)
|
||||
Loading…
Add table
Add a link
Reference in a new issue