diff --git a/Auth/src/main/kotlin/com/infomaniak/core/auth/BaseCredentialManager.kt b/Auth/src/main/kotlin/com/infomaniak/core/auth/BaseCredentialManager.kt
index c1d0696d4..18a6bc956 100644
--- a/Auth/src/main/kotlin/com/infomaniak/core/auth/BaseCredentialManager.kt
+++ b/Auth/src/main/kotlin/com/infomaniak/core/auth/BaseCredentialManager.kt
@@ -19,6 +19,7 @@ package com.infomaniak.core.auth
import androidx.annotation.CallSuper
import androidx.collection.ArrayMap
+import com.infomaniak.core.auth.models.user.Card
import com.infomaniak.core.auth.models.user.User
import com.infomaniak.core.auth.room.UserDatabase
import com.infomaniak.core.network.networking.HttpClientConfig
@@ -58,8 +59,8 @@ abstract class BaseCredentialManager : UserExistenceChecker {
}
@CallSuper
- open suspend fun updateUser(user: User) {
- userDatabase.userDao().update(user)
+ open suspend fun updateUserCard(userId: Int, card: Card?) {
+ userDatabase.userDao().updateUserCard(userId, card)
}
//endregion
diff --git a/Auth/src/main/kotlin/com/infomaniak/core/auth/room/UserDao.kt b/Auth/src/main/kotlin/com/infomaniak/core/auth/room/UserDao.kt
index 790418bf2..afee982e1 100644
--- a/Auth/src/main/kotlin/com/infomaniak/core/auth/room/UserDao.kt
+++ b/Auth/src/main/kotlin/com/infomaniak/core/auth/room/UserDao.kt
@@ -24,6 +24,7 @@ import androidx.room.Insert
import androidx.room.Query
import androidx.room.Update
import androidx.room.Upsert
+import com.infomaniak.core.auth.models.user.Card
import com.infomaniak.core.auth.models.user.User
import kotlinx.coroutines.flow.Flow
@@ -72,6 +73,9 @@ interface UserDao {
@Update
suspend fun update(user: User)
+ @Query("UPDATE user SET card = :card WHERE id = :userId")
+ suspend fun updateUserCard(userId: Int, card: Card?)
+
@Upsert
suspend fun upsert(user: User)
diff --git a/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/ContactCardViewModel.kt b/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/ContactCardViewModel.kt
new file mode 100644
index 000000000..0568e3558
--- /dev/null
+++ b/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/ContactCardViewModel.kt
@@ -0,0 +1,239 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.core.ui.compose.contactcard
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.viewModelScope
+import com.infomaniak.core.auth.UserAccountUtils
+import com.infomaniak.core.auth.models.user.Card
+import com.infomaniak.core.auth.models.user.CardLink
+import com.infomaniak.core.auth.models.user.CardLinkType
+import com.infomaniak.core.auth.models.user.User
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import java.util.UUID
+
+class ContactCardViewModel(
+ application: Application,
+ savedStateHandle: SavedStateHandle,
+) : AndroidViewModel(application) {
+
+ private val accountUtils = UserAccountUtils(application.applicationContext)
+ private val userId: Int = requireNotNull(savedStateHandle.get(USER_ID_KEY)) { "userId argument is required" }
+
+ private val _uiState = MutableStateFlow(ContactCardUiState.Loading)
+ val uiState: StateFlow = _uiState.asStateFlow()
+
+ private var currentUser: User? = null
+
+ init {
+ loadUser()
+ }
+
+ fun loadUser() {
+ viewModelScope.launch {
+ val user = accountUtils.getUserById(userId)
+ currentUser = user
+ if (_uiState.value !is ContactCardUiState.Editing) {
+ _uiState.value = user?.toUiState() ?: ContactCardUiState.Error
+ }
+ }
+ }
+
+ fun startCreate() {
+ val user = currentUser ?: return
+ val editor = ContactCardEditorState.fromUser(user)
+ _uiState.value = ContactCardUiState.Editing(
+ user = user,
+ editor = editor,
+ existingCard = null,
+ isValid = editor.validate(),
+ )
+ }
+
+ fun startEdit(card: Card) {
+ val user = currentUser ?: return
+ val editor = ContactCardEditorState.fromCard(card, user.avatar)
+ _uiState.value = ContactCardUiState.Editing(
+ user = user,
+ editor = editor,
+ existingCard = card,
+ isValid = editor.validate(),
+ )
+ }
+
+ fun cancelEditing() {
+ _uiState.value = currentUser?.toUiState() ?: ContactCardUiState.Error
+ }
+
+ fun updateDraft(editor: ContactCardEditorState) {
+ val current = _uiState.value as? ContactCardUiState.Editing ?: return
+ _uiState.value = current.copy(editor = editor, isValid = editor.validate())
+ }
+
+ fun addAdditionalUrl() {
+ val current = _uiState.value as? ContactCardUiState.Editing ?: return
+ updateDraft(current.editor.copy(additionalUrls = current.editor.additionalUrls + EditableUrl()))
+ }
+
+ fun removeAdditionalUrl(id: String) {
+ val current = _uiState.value as? ContactCardUiState.Editing ?: return
+ updateDraft(current.editor.copy(additionalUrls = current.editor.additionalUrls.filterNot { it.id == id }))
+ }
+
+ fun saveDraft() {
+ val current = _uiState.value as? ContactCardUiState.Editing ?: return
+
+ if (!current.editor.validate()) return
+
+ viewModelScope.launch {
+ val card = current.editor.toCard()
+ accountUtils.updateUserCard(userId, card)
+ val updatedUser = current.user.copy(card = card)
+ currentUser = updatedUser
+ _uiState.value = ContactCardUiState.Preview(user = updatedUser, card = card)
+ }
+ }
+
+ fun deleteCard() {
+ val current = _uiState.value as? ContactCardUiState.Preview ?: return
+
+ viewModelScope.launch {
+ accountUtils.updateUserCard(userId, null)
+ val updatedUser = current.user.copy(card = null)
+ currentUser = updatedUser
+ _uiState.value = ContactCardUiState.Onboarding(updatedUser)
+ }
+ }
+
+ private fun User.toUiState(): ContactCardUiState {
+ return card?.let { ContactCardUiState.Preview(user = this, card = it) } ?: ContactCardUiState.Onboarding(this)
+ }
+
+ companion object {
+ const val USER_ID_KEY = "userId"
+ }
+}
+
+sealed interface ContactCardUiState {
+ data object Loading : ContactCardUiState
+ data object Error : ContactCardUiState
+ data class Onboarding(val user: User) : ContactCardUiState
+ data class Preview(val user: User, val card: Card) : ContactCardUiState
+ data class Editing(
+ val user: User,
+ val editor: ContactCardEditorState,
+ val existingCard: Card?,
+ val isValid: Boolean = false,
+ ) : ContactCardUiState
+}
+
+data class ContactCardEditorState(
+ val firstName: String,
+ val lastName: String,
+ val email: String,
+ val phone: String,
+ val company: String,
+ val avatarUrl: String?,
+ val linkedIn: String,
+ val x: String,
+ val instagram: String,
+ val facebook: String,
+ val website: String,
+ val additionalUrls: List,
+) {
+ fun validate(): Boolean {
+ return firstName.trim().isNotEmpty() &&
+ lastName.trim().isNotEmpty() &&
+ email.trim().isNotEmpty() &&
+ phone.trim().isNotEmpty()
+ }
+
+ fun toCard(): Card {
+ val links = buildList {
+ website.trim().takeIf(String::isNotEmpty)?.let { add(CardLink(CardLinkType.Website, it)) }
+ linkedIn.trim().takeIf(String::isNotEmpty)?.let { add(CardLink(CardLinkType.LinkedIn, it)) }
+ facebook.trim().takeIf(String::isNotEmpty)?.let { add(CardLink(CardLinkType.Facebook, it)) }
+ instagram.trim().takeIf(String::isNotEmpty)?.let { add(CardLink(CardLinkType.Instagram, it)) }
+ x.trim().takeIf(String::isNotEmpty)?.let { add(CardLink(CardLinkType.X, it)) }
+ additionalUrls.mapNotNull { it.value.trim().takeIf(String::isNotEmpty) }.forEach {
+ add(CardLink(CardLinkType.Other, it))
+ }
+ }.takeIf { it.isNotEmpty() }
+
+ return Card(
+ firstName = firstName.trim(),
+ lastName = lastName.trim(),
+ email = email.trim(),
+ phone = phone.trim(),
+ company = company.trim().takeIf(String::isNotBlank),
+ avatarUrl = avatarUrl?.takeIf(String::isNotBlank),
+ links = links,
+ )
+ }
+
+ companion object {
+ fun fromUser(user: User): ContactCardEditorState {
+ return ContactCardEditorState(
+ firstName = user.firstname,
+ lastName = user.lastname,
+ email = user.email,
+ phone = "",
+ company = "",
+ avatarUrl = user.avatar,
+ linkedIn = "",
+ x = "",
+ instagram = "",
+ facebook = "",
+ website = "",
+ additionalUrls = emptyList(),
+ )
+ }
+
+ fun fromCard(card: Card, fallbackAvatarUrl: String?): ContactCardEditorState {
+ val websiteLinks = card.links.orEmpty().filter { it.type == CardLinkType.Website }
+ val otherUrls = buildList {
+ websiteLinks.drop(1).forEach { add(it.url) }
+ card.links.orEmpty().filter { it.type == CardLinkType.Other }.forEach { add(it.url) }
+ }
+
+ return ContactCardEditorState(
+ firstName = card.firstName,
+ lastName = card.lastName,
+ email = card.email,
+ phone = card.phone,
+ company = card.company.orEmpty(),
+ avatarUrl = card.avatarUrl ?: fallbackAvatarUrl,
+ linkedIn = card.links.orEmpty().firstOrNull { it.type == CardLinkType.LinkedIn }?.url.orEmpty(),
+ x = card.links.orEmpty().firstOrNull { it.type == CardLinkType.X }?.url.orEmpty(),
+ instagram = card.links.orEmpty().firstOrNull { it.type == CardLinkType.Instagram }?.url.orEmpty(),
+ facebook = card.links.orEmpty().firstOrNull { it.type == CardLinkType.Facebook }?.url.orEmpty(),
+ website = websiteLinks.firstOrNull()?.url.orEmpty(),
+ additionalUrls = otherUrls.map { EditableUrl(value = it) },
+ )
+ }
+ }
+}
+
+data class EditableUrl(
+ val id: String = UUID.randomUUID().toString(),
+ val value: String = "",
+)
diff --git a/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/ContactVCardBloc.kt b/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/ContactVCardBloc.kt
new file mode 100644
index 000000000..c91056002
--- /dev/null
+++ b/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/ContactVCardBloc.kt
@@ -0,0 +1,105 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.core.ui.compose.contactcard.component
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import com.infomaniak.core.auth.models.user.Card
+import com.infomaniak.core.auth.models.user.CardLinkType
+import com.infomaniak.core.auth.models.user.User
+import com.infomaniak.core.ui.compose.margin.Margin
+
+@Composable
+internal fun ContactVCardBloc(user: User, card: Card) {
+ Surface(
+ modifier = Modifier.fillMaxWidth(),
+ shape = RoundedCornerShape(CardCornerRadius),
+ color = MaterialTheme.colorScheme.surface,
+ ) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ QrCodeHeader(user = user, card = card)
+ Spacer(Modifier.height(Margin.Medium))
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = Margin.Medium),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text(
+ text = "${card.firstName} ${card.lastName}".trim(),
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold,
+ color = MaterialTheme.colorScheme.onSurface,
+ )
+ Spacer(Modifier.height(Margin.Mini))
+ Text(
+ text = card.email,
+ color = MaterialTheme.colorScheme.primary,
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+
+ Spacer(Modifier.height(Margin.Medium))
+
+ ContactInfoRows(
+ card = card,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = Margin.Medium),
+ )
+
+ val links = card.links.orEmpty().filter { it.url.isNotBlank() && it.type != CardLinkType.Website }
+ if (links.isNotEmpty()) {
+ Spacer(Modifier.height(Margin.Medium))
+ LinksRow(
+ links = links,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = Margin.Medium, vertical = Margin.Small),
+ )
+ } else {
+ Spacer(Modifier.height(Margin.Medium))
+ }
+ }
+ }
+}
+
+@Preview(name = "ContactVCardBloc")
+@Composable
+private fun ContactVCardBlocPreview() {
+ MaterialTheme {
+ Surface {
+ ContactVCardBloc(
+ user = previewUser(),
+ card = previewCard(),
+ )
+ }
+ }
+}
diff --git a/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/LinksRow.kt b/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/LinksRow.kt
new file mode 100644
index 000000000..fb0d5b5aa
--- /dev/null
+++ b/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/LinksRow.kt
@@ -0,0 +1,91 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.core.ui.compose.contactcard.component
+
+import androidx.annotation.DrawableRes
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.infomaniak.core.auth.models.user.CardLink
+import com.infomaniak.core.auth.models.user.CardLinkType
+import com.infomaniak.core.ui.compose.contactcard.R
+import com.infomaniak.core.ui.compose.margin.Margin
+
+@Composable
+internal fun LinksRow(links: List, modifier: Modifier = Modifier) {
+ val grouped = links.groupBy { it.type }
+ Row(
+ modifier = modifier,
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.spacedBy(Margin.Small),
+ ) {
+ CardLinkType.entries.forEach { type ->
+ val linksOfType = grouped[type] ?: return@forEach
+ Icon(
+ imageVector = ImageVector.vectorResource(type.iconRes()),
+ contentDescription = stringResource(R.string.socialNetworksIconContentDescription),
+ tint = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.size(22.dp),
+ )
+ if (type == CardLinkType.Other && linksOfType.size > 1) {
+ Text(
+ text = "+${linksOfType.size - 1}",
+ color = MaterialTheme.colorScheme.primary,
+ style = MaterialTheme.typography.bodyMedium,
+ fontWeight = FontWeight.SemiBold,
+ )
+ }
+ }
+ }
+}
+
+@DrawableRes
+private fun CardLinkType.iconRes(): Int = when (this) {
+ CardLinkType.LinkedIn -> R.drawable.ic_linkedin
+ CardLinkType.Facebook -> R.drawable.ic_facebook
+ CardLinkType.Instagram -> R.drawable.ic_instagram
+ CardLinkType.X -> R.drawable.ic_x
+ CardLinkType.Other, CardLinkType.Website -> R.drawable.ic_link
+}
+
+@Preview(name = "LinksRow")
+@Composable
+private fun LinksRowPreview() {
+ MaterialTheme {
+ Surface {
+ LinksRow(
+ links = listOf(
+ CardLink(CardLinkType.LinkedIn, "https://linkedin.com"),
+ CardLink(CardLinkType.Other, "https://blog.example.com"),
+ ),
+ )
+ }
+ }
+}
diff --git a/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/QrCodeHeader.kt b/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/QrCodeHeader.kt
new file mode 100644
index 000000000..a9230ec2e
--- /dev/null
+++ b/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/QrCodeHeader.kt
@@ -0,0 +1,155 @@
+/*
+ * Infomaniak Core - Android
+ * Copyright (C) 2026 Infomaniak Network SA
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+package com.infomaniak.core.ui.compose.contactcard.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import com.infomaniak.core.auth.models.user.Card
+import com.infomaniak.core.auth.models.user.User
+import com.infomaniak.core.avatar.components.Avatar
+import com.infomaniak.core.avatar.models.AvatarType
+import com.infomaniak.core.ui.compose.contactcard.R
+import com.infomaniak.core.ui.compose.margin.Margin
+import io.github.alexzhirkevich.qrose.QrCodePainter
+import com.infomaniak.core.common.R as RCore
+@Composable
+internal fun QrCodeHeader(user: User, card: Card) {
+ BoxWithConstraints(
+ modifier = Modifier.fillMaxWidth(),
+ contentAlignment = Alignment.TopCenter,
+ ) {
+ val qrSize = (maxWidth * 0.62f).coerceAtMost(240.dp)
+ val gradientHeight = qrSize * 0.4f
+
+ HeaderBackground(
+ qrSize = qrSize,
+ gradientHeight = gradientHeight
+ )
+
+ Surface(
+ shape = RoundedCornerShape(CardCornerRadius),
+ modifier = Modifier.padding(top = gradientHeight * 0.5f),
+ color = Color.White,
+ shadowElevation = 2.dp,
+ ) {
+ Box(
+ modifier = Modifier
+ .size(qrSize)
+ .padding(Margin.Small),
+ contentAlignment = Alignment.Center,
+ ) {
+ QrCodeImage(card = card)
+ QrCodeAvatar(user = user, qrSize = qrSize)
+ }
+ }
+ }
+}
+
+@Composable
+private fun HeaderBackground(qrSize: Dp, gradientHeight: Dp) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(qrSize),
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(gradientHeight)
+ .background(MaterialTheme.colorScheme.primary),
+ )
+ }
+}
+
+@Composable
+private fun QrCodeImage(card: Card) {
+ val vCardData = card.makeVCardString(forQRCode = true)
+ val qrPainter = remember(vCardData) {
+ runCatching { QrCodePainter(data = vCardData) }.getOrNull()
+ }
+
+ if (qrPainter != null) {
+ Image(
+ painter = qrPainter,
+ contentDescription = stringResource(R.string.contactCardQrCodeDescription),
+ modifier = Modifier.fillMaxSize(),
+ )
+ } else {
+ Text(
+ text = stringResource(RCore.string.anErrorHasOccurred),
+ color = MaterialTheme.colorScheme.error,
+ )
+ }
+}
+
+@Composable
+private fun QrCodeAvatar(user: User, qrSize: Dp) {
+ Surface(
+ shape = CircleShape,
+ color = MaterialTheme.colorScheme.surface,
+ modifier = Modifier
+ .size(qrSize * 0.24f)
+ .clip(CircleShape),
+ ) {
+ Box(
+ modifier = Modifier.padding(3.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Avatar(
+ avatarType = AvatarType.fromUser(user),
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(CircleShape),
+ )
+ }
+ }
+}
+
+@Preview(name = "QrCodeHeader")
+@Composable
+private fun QrCodeHeaderPreview() {
+ MaterialTheme {
+ Surface {
+ QrCodeHeader(
+ user = previewUser(),
+ card = previewCard(),
+ )
+ }
+ }
+}
diff --git a/Ui/Compose/ContactCard/src/main/res/values-da/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-da/strings.xml
index e86eb138d..880b87864 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-da/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-da/strings.xml
@@ -30,6 +30,7 @@
Anden URL
Telefon
Del
+ Ikon for sociale netværk
Hjemmeside
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values-de/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-de/strings.xml
index 8fc1ced43..6e6d620a9 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-de/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-de/strings.xml
@@ -30,6 +30,7 @@
Andere URL
Telefon
Teilen
+ Symbol für soziale Netzwerke
Website
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values-el/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-el/strings.xml
index 6fb5f35f0..60eb20f44 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-el/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-el/strings.xml
@@ -30,6 +30,7 @@
Άλλη διεύθυνση URL
Τηλέφωνο
Κοινοποίηση
+ Εικονίδιο κοινωνικών δικτύων
Ιστοσελίδα
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values-es/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-es/strings.xml
index e62c1f0f5..40dffd339 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-es/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-es/strings.xml
@@ -30,6 +30,7 @@
Otra URL
Teléfono
Compartir
+ Icono de redes sociales
Página web
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values-fi/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-fi/strings.xml
index 837b49f3c..46c4c7a9c 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-fi/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-fi/strings.xml
@@ -30,6 +30,7 @@
Muu URL-osoite
Puhelin
Jaa
+ Sosiaalisten verkkojen kuvake
Verkkosivusto
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values-fr/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-fr/strings.xml
index 2707e5a1c..688674990 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-fr/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-fr/strings.xml
@@ -30,6 +30,7 @@
Autre URL
Téléphone
Partager
+ Icône des réseaux sociaux
Site web
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values-it/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-it/strings.xml
index 792082156..235f7f989 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-it/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-it/strings.xml
@@ -30,6 +30,7 @@
Altro URL
Telefono
Condividi
+ Icona dei social network
Sito web
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values-nb/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-nb/strings.xml
index 08c7a17b2..782bd6ef1 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-nb/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-nb/strings.xml
@@ -30,6 +30,7 @@
Annen URL
Telefon
Del
+ Ikon for sosiale nettverk
Nettsted
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values-nl/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-nl/strings.xml
index cf995b278..840d4eef8 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-nl/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-nl/strings.xml
@@ -30,6 +30,7 @@
Andere URL
Telefoon
Delen
+ Icoon voor sociale netwerken
Website
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values-pl/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-pl/strings.xml
index d5eb62067..c72e19b0e 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-pl/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-pl/strings.xml
@@ -30,6 +30,7 @@
Inny adres URL
Telefon
Udostępnij
+ Ikona sieci społecznościowych
Strona internetowa
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values-pt/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-pt/strings.xml
index 84aac160c..663be56d4 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-pt/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-pt/strings.xml
@@ -30,6 +30,7 @@
Outro URL
Telefone
Partilhar
+ Ícone das redes sociais
Site
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values-sv/strings.xml b/Ui/Compose/ContactCard/src/main/res/values-sv/strings.xml
index c34168992..c5103eaa3 100644
--- a/Ui/Compose/ContactCard/src/main/res/values-sv/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values-sv/strings.xml
@@ -30,6 +30,7 @@
Annan URL
Telefon
Dela
+ Ikon för sociala nätverk
Webbplats
X
diff --git a/Ui/Compose/ContactCard/src/main/res/values/strings.xml b/Ui/Compose/ContactCard/src/main/res/values/strings.xml
index c1f1672ab..f12ca6b38 100644
--- a/Ui/Compose/ContactCard/src/main/res/values/strings.xml
+++ b/Ui/Compose/ContactCard/src/main/res/values/strings.xml
@@ -46,6 +46,7 @@
Other URL
Phone
Share
+ Social networks icon
Web site
X