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
+2 -1
View File
@@ -7,6 +7,7 @@
*/
plugins {
id("io.element.android-compose-library")
id("kotlin-parcelize")
}
android {
@@ -16,7 +17,7 @@ android {
dependencies {
api(libs.kotlinx.collections.immutable)
implementation(libs.coroutines.core)
implementation(projects.libraries.architecture)
api(projects.libraries.architecture)
implementation(projects.libraries.designsystem)
api(projects.libraries.matrix.api)
}
@@ -0,0 +1,33 @@
/*
* Copyright (c) 2026 Novaloop AG
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.healthchat.api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
/**
* Renders the HealthChat account status banners ("account under review", "profile incomplete").
*
* Both states resolve server-side (approval, completed profile), so the banners are
* deliberately not dismissible. When nothing needs attention, nothing is rendered.
*/
fun interface HealthChatAccountStatusRenderer {
/**
* @param onCompleteProfileClick called when the user taps the banner action to complete their profile.
* @param includePendingApproval false when the host screen already shows its own
* pending-approval banner (e.g. the member directory with the verbatim server message).
* @param modifier the modifier to apply to the banner.
*/
@Composable
fun Render(
onCompleteProfileClick: () -> Unit,
includePendingApproval: Boolean,
modifier: Modifier,
)
}
@@ -0,0 +1,45 @@
/*
* Copyright (c) 2026 Novaloop AG
* Copyright (c) 2026 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.healthchat.api
import android.os.Parcelable
import com.bumble.appyx.core.modality.BuildContext
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 kotlinx.parcelize.Parcelize
/**
* Entry point into the fork-owned HealthChat flow (profile editor, member invitation).
*/
fun interface HealthChatEntryPoint : FeatureEntryPoint {
data class Params(
val initialTarget: InitialTarget,
) : NodeInputs
sealed interface InitialTarget : Parcelable {
@Parcelize
data object EditOwnProfile : InitialTarget
@Parcelize
data object Invite : InitialTarget
}
interface Callback : Plugin {
fun onDone()
}
fun createNode(
parentNode: Node,
buildContext: BuildContext,
params: Params,
callback: Callback,
): Node
}
@@ -37,4 +37,46 @@
<string name="healthchat_a11y_refresh">Aktualisieren</string>
<string name="healthchat_a11y_edit_profile">Profil bearbeiten</string>
<string name="healthchat_a11y_invite_member">Neues Mitglied einladen</string>
<string name="healthchat_field_name">Anzeigename</string>
<string name="healthchat_field_title">Titel</string>
<string name="healthchat_field_title_placeholder">z.B. Dr. med.</string>
<string name="healthchat_field_first_name">Vorname</string>
<string name="healthchat_field_last_name">Nachname</string>
<string name="healthchat_field_specialty_placeholder">z.B. Allgemeinmedizin</string>
<string name="healthchat_field_practice_name_placeholder">z.B. Praxis Muster</string>
<string name="healthchat_field_city">Ort</string>
<string name="healthchat_field_city_placeholder">z.B. Zürich</string>
<string name="healthchat_field_business_email_placeholder">praxis@beispiel.ch</string>
<string name="healthchat_field_business_tel_placeholder">+41 44 123 45 67</string>
<!-- Swiss German: "Strasse" (no ß) is correct for this product. -->
<string name="healthchat_field_street" tools:ignore="Typos">Strasse</string>
<string name="healthchat_field_house_number">Nr.</string>
<string name="healthchat_field_postal_code">PLZ</string>
<string name="healthchat_field_website_placeholder">https://praxis-muster.ch</string>
<string name="healthchat_screen_edit_profile_title">Profil bearbeiten</string>
<string name="healthchat_screen_edit_profile_section_personal">Persönliche Angaben</string>
<string name="healthchat_screen_edit_profile_section_professional">Berufliche Angaben</string>
<string name="healthchat_screen_edit_profile_section_contact">Kontaktdaten</string>
<string name="healthchat_screen_edit_profile_section_address">Praxisadresse</string>
<string name="healthchat_screen_edit_profile_saving">Profil wird gespeichert…</string>
<string name="healthchat_screen_edit_profile_error_title">Profil konnte nicht gespeichert werden</string>
<string name="healthchat_screen_edit_profile_load_error">Das Profil konnte nicht geladen werden.</string>
<string name="healthchat_screen_edit_profile_recommended">Für ein vollständiges Profil empfohlen</string>
<string name="healthchat_error_field_required">Dieses Feld ist erforderlich</string>
<string name="healthchat_error_invalid_email">Bitte eine gültige E-Mail-Adresse eingeben</string>
<string name="healthchat_error_invalid_url">Bitte eine gültige URL eingeben (z.B. https://praxis-muster.ch)</string>
<string name="healthchat_error_note_too_short">Die persönliche Nachricht muss mindestens 10 Zeichen lang sein</string>
<string name="healthchat_error_avatar_too_large">Das Bild darf höchstens 2 MB gross sein.</string>
<string name="healthchat_banner_incomplete_title">Profil unvollständig</string>
<string name="healthchat_banner_incomplete_description">Bitte vervollständigen Sie Ihr Profil, damit andere Mitglieder Sie finden können. Fehlende Angaben: %1$s</string>
<string name="healthchat_banner_incomplete_action">Profil vervollständigen</string>
<string name="healthchat_screen_invite_title">Mitglied einladen</string>
<string name="healthchat_screen_invite_description">Laden Sie neue Mitglieder zu HealthChat ein, indem Sie deren E-Mail-Adresse und eine persönliche Nachricht eingeben.</string>
<string name="healthchat_screen_invite_email_label">E-Mail-Adresse</string>
<string name="healthchat_screen_invite_email_placeholder">beispiel@domain.ch</string>
<string name="healthchat_screen_invite_note_label">Persönliche Nachricht</string>
<string name="healthchat_screen_invite_note_placeholder">Schreiben Sie eine persönliche Nachricht an das neue Mitglied…</string>
<string name="healthchat_screen_invite_note_counter">%1$d/10 Zeichen</string>
<string name="healthchat_screen_invite_send">Einladung senden</string>
<string name="healthchat_screen_invite_sending">Einladung wird gesendet…</string>
</resources>
@@ -38,4 +38,45 @@
<string name="healthchat_a11y_refresh">Actualiser</string>
<string name="healthchat_a11y_edit_profile">Modifier le profil</string>
<string name="healthchat_a11y_invite_member">Inviter un nouveau membre</string>
<string name="healthchat_field_name">Nom affiché</string>
<string name="healthchat_field_title">Titre</string>
<string name="healthchat_field_title_placeholder">p. ex. Dr méd.</string>
<string name="healthchat_field_first_name">Prénom</string>
<string name="healthchat_field_last_name">Nom</string>
<string name="healthchat_field_specialty_placeholder">p. ex. médecine générale</string>
<string name="healthchat_field_practice_name_placeholder">p. ex. Praxis Muster</string>
<string name="healthchat_field_city">Ville</string>
<string name="healthchat_field_city_placeholder">p. ex. Zurich</string>
<string name="healthchat_field_business_email_placeholder">cabinet@exemple.ch</string>
<string name="healthchat_field_business_tel_placeholder">+41 44 123 45 67</string>
<string name="healthchat_field_street">Rue</string>
<string name="healthchat_field_house_number">N°</string>
<string name="healthchat_field_postal_code">NPA</string>
<string name="healthchat_field_website_placeholder">https://praxis-muster.ch</string>
<string name="healthchat_screen_edit_profile_title">Modifier le profil</string>
<string name="healthchat_screen_edit_profile_section_personal">Informations personnelles</string>
<string name="healthchat_screen_edit_profile_section_professional">Informations professionnelles</string>
<string name="healthchat_screen_edit_profile_section_contact">Coordonnées</string>
<string name="healthchat_screen_edit_profile_section_address">Adresse du cabinet</string>
<string name="healthchat_screen_edit_profile_saving">Enregistrement du profil…</string>
<string name="healthchat_screen_edit_profile_error_title">Impossible d’enregistrer le profil</string>
<string name="healthchat_screen_edit_profile_load_error">Le profil n’a pas pu être chargé.</string>
<string name="healthchat_screen_edit_profile_recommended">Recommandé pour un profil complet</string>
<string name="healthchat_error_field_required">Ce champ est obligatoire</string>
<string name="healthchat_error_invalid_email">Veuillez saisir une adresse e-mail valide</string>
<string name="healthchat_error_invalid_url">Veuillez saisir une URL valide (p. ex. https://praxis-muster.ch)</string>
<string name="healthchat_error_note_too_short">Le message personnel doit contenir au moins 10 caractères</string>
<string name="healthchat_error_avatar_too_large">L’image ne doit pas dépasser 2 Mo.</string>
<string name="healthchat_banner_incomplete_title">Profil incomplet</string>
<string name="healthchat_banner_incomplete_description">Veuillez compléter votre profil pour que les autres membres puissent vous trouver. Informations manquantes : %1$s</string>
<string name="healthchat_banner_incomplete_action">Compléter le profil</string>
<string name="healthchat_screen_invite_title">Inviter un membre</string>
<string name="healthchat_screen_invite_description">Invitez de nouveaux membres sur HealthChat en saisissant leur adresse e-mail et un message personnel.</string>
<string name="healthchat_screen_invite_email_label">Adresse e-mail</string>
<string name="healthchat_screen_invite_email_placeholder">exemple@domaine.ch</string>
<string name="healthchat_screen_invite_note_label">Message personnel</string>
<string name="healthchat_screen_invite_note_placeholder">Écrivez un message personnel au nouveau membre…</string>
<string name="healthchat_screen_invite_note_counter">%1$d/10 caractères</string>
<string name="healthchat_screen_invite_send">Envoyer l’invitation</string>
<string name="healthchat_screen_invite_sending">Envoi de l’invitation…</string>
</resources>
@@ -38,4 +38,45 @@
<string name="healthchat_a11y_refresh">Aggiorna</string>
<string name="healthchat_a11y_edit_profile">Modifica profilo</string>
<string name="healthchat_a11y_invite_member">Invita un nuovo membro</string>
<string name="healthchat_field_name">Nome visualizzato</string>
<string name="healthchat_field_title">Titolo</string>
<string name="healthchat_field_title_placeholder">ad es. Dr. med.</string>
<string name="healthchat_field_first_name">Nome</string>
<string name="healthchat_field_last_name">Cognome</string>
<string name="healthchat_field_specialty_placeholder">ad es. medicina generale</string>
<string name="healthchat_field_practice_name_placeholder">ad es. Praxis Muster</string>
<string name="healthchat_field_city">Città</string>
<string name="healthchat_field_city_placeholder">ad es. Zurigo</string>
<string name="healthchat_field_business_email_placeholder">studio@esempio.ch</string>
<string name="healthchat_field_business_tel_placeholder">+41 44 123 45 67</string>
<string name="healthchat_field_street">Via</string>
<string name="healthchat_field_house_number">N.</string>
<string name="healthchat_field_postal_code">CAP</string>
<string name="healthchat_field_website_placeholder">https://praxis-muster.ch</string>
<string name="healthchat_screen_edit_profile_title">Modifica profilo</string>
<string name="healthchat_screen_edit_profile_section_personal">Dati personali</string>
<string name="healthchat_screen_edit_profile_section_professional">Dati professionali</string>
<string name="healthchat_screen_edit_profile_section_contact">Contatti</string>
<string name="healthchat_screen_edit_profile_section_address">Indirizzo dello studio</string>
<string name="healthchat_screen_edit_profile_saving">Salvataggio del profilo…</string>
<string name="healthchat_screen_edit_profile_error_title">Impossibile salvare il profilo</string>
<string name="healthchat_screen_edit_profile_load_error">Impossibile caricare il profilo.</string>
<string name="healthchat_screen_edit_profile_recommended">Consigliato per un profilo completo</string>
<string name="healthchat_error_field_required">Questo campo è obbligatorio</string>
<string name="healthchat_error_invalid_email">Inserisci un indirizzo e-mail valido</string>
<string name="healthchat_error_invalid_url">Inserisci un URL valido (ad es. https://praxis-muster.ch)</string>
<string name="healthchat_error_note_too_short">Il messaggio personale deve contenere almeno 10 caratteri</string>
<string name="healthchat_error_avatar_too_large">L’immagine non deve superare i 2 MB.</string>
<string name="healthchat_banner_incomplete_title">Profilo incompleto</string>
<string name="healthchat_banner_incomplete_description">Completa il tuo profilo affinché gli altri membri possano trovarti. Dati mancanti: %1$s</string>
<string name="healthchat_banner_incomplete_action">Completa il profilo</string>
<string name="healthchat_screen_invite_title">Invita un membro</string>
<string name="healthchat_screen_invite_description">Invita nuovi membri su HealthChat inserendo il loro indirizzo e-mail e un messaggio personale.</string>
<string name="healthchat_screen_invite_email_label">Indirizzo e-mail</string>
<string name="healthchat_screen_invite_email_placeholder">esempio@dominio.ch</string>
<string name="healthchat_screen_invite_note_label">Messaggio personale</string>
<string name="healthchat_screen_invite_note_placeholder">Scrivi un messaggio personale al nuovo membro…</string>
<string name="healthchat_screen_invite_note_counter">%1$d/10 caratteri</string>
<string name="healthchat_screen_invite_send">Invia invito</string>
<string name="healthchat_screen_invite_sending">Invio dell’invito…</string>
</resources>
@@ -38,4 +38,45 @@
<string name="healthchat_a11y_refresh">Refresh</string>
<string name="healthchat_a11y_edit_profile">Edit profile</string>
<string name="healthchat_a11y_invite_member">Invite a new member</string>
<string name="healthchat_field_name">Display name</string>
<string name="healthchat_field_title">Title</string>
<string name="healthchat_field_title_placeholder">e.g. Dr. med.</string>
<string name="healthchat_field_first_name">First name</string>
<string name="healthchat_field_last_name">Last name</string>
<string name="healthchat_field_specialty_placeholder">e.g. general medicine</string>
<string name="healthchat_field_practice_name_placeholder">e.g. Praxis Muster</string>
<string name="healthchat_field_city">City</string>
<string name="healthchat_field_city_placeholder">e.g. Zurich</string>
<string name="healthchat_field_business_email_placeholder">praxis@example.ch</string>
<string name="healthchat_field_business_tel_placeholder">+41 44 123 45 67</string>
<string name="healthchat_field_street">Street</string>
<string name="healthchat_field_house_number">No.</string>
<string name="healthchat_field_postal_code">Postal code</string>
<string name="healthchat_field_website_placeholder">https://praxis-muster.ch</string>
<string name="healthchat_screen_edit_profile_title">Edit profile</string>
<string name="healthchat_screen_edit_profile_section_personal">Personal details</string>
<string name="healthchat_screen_edit_profile_section_professional">Professional details</string>
<string name="healthchat_screen_edit_profile_section_contact">Contact details</string>
<string name="healthchat_screen_edit_profile_section_address">Practice address</string>
<string name="healthchat_screen_edit_profile_saving">Saving profile…</string>
<string name="healthchat_screen_edit_profile_error_title">Couldn’t save the profile</string>
<string name="healthchat_screen_edit_profile_load_error">The profile couldn’t be loaded.</string>
<string name="healthchat_screen_edit_profile_recommended">Recommended for a complete profile</string>
<string name="healthchat_error_field_required">This field is required</string>
<string name="healthchat_error_invalid_email">Please enter a valid email address</string>
<string name="healthchat_error_invalid_url">Please enter a valid URL (e.g. https://praxis-muster.ch)</string>
<string name="healthchat_error_note_too_short">The personal message must be at least 10 characters long</string>
<string name="healthchat_error_avatar_too_large">The image must not exceed 2 MB.</string>
<string name="healthchat_banner_incomplete_title">Profile incomplete</string>
<string name="healthchat_banner_incomplete_description">Please complete your profile so other members can find you. Missing details: %1$s</string>
<string name="healthchat_banner_incomplete_action">Complete profile</string>
<string name="healthchat_screen_invite_title">Invite a member</string>
<string name="healthchat_screen_invite_description">Invite new members to HealthChat by entering their email address and a personal message.</string>
<string name="healthchat_screen_invite_email_label">Email address</string>
<string name="healthchat_screen_invite_email_placeholder">example@domain.ch</string>
<string name="healthchat_screen_invite_note_label">Personal message</string>
<string name="healthchat_screen_invite_note_placeholder">Write a personal message to the new member…</string>
<string name="healthchat_screen_invite_note_counter">%1$d/10 characters</string>
<string name="healthchat_screen_invite_send">Send invitation</string>
<string name="healthchat_screen_invite_sending">Sending invitation…</string>
</resources>