Add profile editor, member invite and status banners (#4)

* feat(healthchat): add profile editor, member invite and status banners

Native 13-field profile editor with diff-only saves and avatar upload
through the HealthChat API (replaces the upstream display-name editor),
email invitation for approved accounts, and non-dismissible account
status banners in settings and the member directory.

* fix(healthchat): satisfy detekt and konsist rules

Konsist presenter helper shape, no '=' in state parameter docs, present-tense invite callback, and complete renderer KDoc.
This commit is contained in:
2026-08-06 13:28:24 +02:00
committed by GitHub
parent edb3520c56
commit 7f43998fa5
74 changed files with 2946 additions and 26 deletions
@@ -34,6 +34,7 @@ import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import im.vector.app.features.analytics.plan.MobileScreen
import io.element.android.annotations.ContributesNode
import io.element.android.features.healthchat.api.HealthChatEntryPoint
import io.element.android.features.healthchat.api.directory.MemberDirectoryRenderer
import io.element.android.features.home.api.HomeEntryPoint
import io.element.android.features.home.impl.components.RoomListMenuAction
@@ -90,8 +91,9 @@ class HomeFlowNode(
private val declineInviteAndBlockUserEntryPoint: DeclineInviteAndBlockEntryPoint,
private val changeRoomMemberRolesEntryPoint: ChangeRoomMemberRolesEntryPoint,
private val leaveRoomRenderer: LeaveRoomRenderer,
// HealthChat fork: renders the member directory tab.
// HealthChat fork: renders the member directory tab and hosts the editor/invite flow.
private val memberDirectoryRenderer: MemberDirectoryRenderer,
private val healthChatEntryPoint: HealthChatEntryPoint,
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
) : BaseFlowNode<HomeFlowNode.NavTarget>(
backstack = BackStack(
@@ -143,6 +145,10 @@ class HomeFlowNode(
@Parcelize
data class SelectNewOwnersWhenLeavingRoom(val roomId: RoomId) : NavTarget
// HealthChat fork: profile editor and member invitation.
@Parcelize
data class HealthChat(val target: HealthChatEntryPoint.InitialTarget) : NavTarget
}
private fun navigateToReportRoom(roomId: RoomId) {
@@ -262,9 +268,12 @@ class HomeFlowNode(
onOpenDm = ::navigateToRoom,
onMemberClick = { callback.navigateToUserProfile(it) },
onMyProfileClick = { callback.navigateToUserProfile(matrixClient.sessionId) },
// Hidden until the native editor and invite screens exist.
onEditProfileClick = null,
onInviteClick = null,
onEditProfileClick = {
backstack.push(NavTarget.HealthChat(HealthChatEntryPoint.InitialTarget.EditOwnProfile))
},
onInviteClick = {
backstack.push(NavTarget.HealthChat(HealthChatEntryPoint.InitialTarget.Invite))
},
modifier = Modifier,
)
},
@@ -313,6 +322,18 @@ class HomeFlowNode(
listType = ChangeRoomMemberRolesListType.SelectNewOwnersWhenLeaving,
)
}
is NavTarget.HealthChat -> {
healthChatEntryPoint.createNode(
parentNode = this,
buildContext = buildContext,
params = HealthChatEntryPoint.Params(navTarget.target),
callback = object : HealthChatEntryPoint.Callback {
override fun onDone() {
backstack.pop()
}
},
)
}
NavTarget.Root -> rootNode(buildContext)
}
}
@@ -41,6 +41,7 @@ class DefaultHomeEntryPointTest : RobolectricTest() {
changeRoomMemberRolesEntryPoint = { _, _, _, _ -> lambdaError() },
leaveRoomRenderer = { _, _, _ -> lambdaError() },
memberDirectoryRenderer = { _, _, _, _, _, _, _, _, _ -> lambdaError() },
healthChatEntryPoint = { _, _, _, _ -> lambdaError() },
sessionCoroutineScope = backgroundScope,
)
}