From c618152b32bf8ad40bf580b0d1c64e4d1eb3f40d Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Fri, 14 Aug 2026 09:33:10 +0200 Subject: [PATCH 01/12] refactor: Remove useless component --- .../contactcard/component/LoadingContent.kt | 44 ------------------- 1 file changed, 44 deletions(-) delete mode 100644 Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/LoadingContent.kt diff --git a/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/LoadingContent.kt b/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/LoadingContent.kt deleted file mode 100644 index 2a8f7d1ad..000000000 --- a/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/LoadingContent.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Surface -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.tooling.preview.Preview - -@Composable -internal fun LoadingContent() { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() - } -} - -@Preview(name = "LoadingContent") -@Composable -private fun LoadingContentPreview() { - MaterialTheme { - Surface { - LoadingContent() - } - } -} From 3036266978378492301b114877378705c22b290b Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Thu, 23 Jul 2026 18:14:43 +0200 Subject: [PATCH 02/12] feat: Add ContactCard UI state types --- .../contactcard/ContactCardViewModel.kt | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/ContactCardViewModel.kt 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..28c1dccb6 --- /dev/null +++ b/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/ContactCardViewModel.kt @@ -0,0 +1,116 @@ +/* + * 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 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 java.util.UUID + +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?) : 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 toCard(fallbackAvatarUrl: String?): 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) ?: fallbackAvatarUrl, + 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 = "", +) From 015919d3c16ec4ab13a90dd65dfb7d193692b82b Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Thu, 23 Jul 2026 18:14:48 +0200 Subject: [PATCH 03/12] feat: Add ContactCardViewModel implementation --- .../contactcard/ContactCardViewModel.kt | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) 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 index 28c1dccb6..aec2cd81e 100644 --- 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 @@ -16,12 +16,117 @@ */ 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 + _uiState.value = ContactCardUiState.Editing( + user = user, + editor = ContactCardEditorState.fromUser(user), + existingCard = null, + ) + } + + fun startEdit(card: Card) { + val user = currentUser ?: return + _uiState.value = ContactCardUiState.Editing( + user = user, + editor = ContactCardEditorState.fromCard(card, user.avatar), + existingCard = card, + ) + } + + 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) + } + + 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 + + viewModelScope.launch { + val card = current.editor.toCard(current.user.avatar) + val updatedUser = current.user.copy(card = card) + accountUtils.updateUser(updatedUser) + currentUser = updatedUser + _uiState.value = ContactCardUiState.Preview(user = updatedUser, card = card) + } + } + + fun deleteCard() { + val current = _uiState.value as? ContactCardUiState.Preview ?: return + + viewModelScope.launch { + val updatedUser = current.user.copy(card = null) + accountUtils.updateUser(updatedUser) + 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 From 68354a80a6f493add3cd8c07c2c831b8b0e0f94e Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Thu, 23 Jul 2026 18:15:12 +0200 Subject: [PATCH 04/12] feat: Add vCard bloc component --- .../contactcard/component/ContactVCardBloc.kt | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/ContactVCardBloc.kt 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(), + ) + } + } +} From eba2570bd452a53a7e30cda8cc3e5342fb85d4f6 Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Thu, 23 Jul 2026 18:15:13 +0200 Subject: [PATCH 05/12] feat: Add links row component --- .../compose/contactcard/component/LinksRow.kt | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/LinksRow.kt 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..2e181d248 --- /dev/null +++ b/Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/LinksRow.kt @@ -0,0 +1,90 @@ +/* + * 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.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 = null, + 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"), + ), + ) + } + } +} From 551b71a4db71832d82f89e0e2da434665495bc9c Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Thu, 23 Jul 2026 18:15:13 +0200 Subject: [PATCH 06/12] feat: Add QR code header component --- .../contactcard/component/QrCodeHeader.kt | 155 ++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 Ui/Compose/ContactCard/src/main/kotlin/com/infomaniak/core/ui/compose/contactcard/component/QrCodeHeader.kt 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(), + ) + } + } +} From f4ab9ea67f340c7afd1d50b6da624e66e5ca5fbc Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Mon, 3 Aug 2026 15:50:34 +0200 Subject: [PATCH 07/12] fix: Update only card column --- .../kotlin/com/infomaniak/core/auth/BaseCredentialManager.kt | 5 +++-- .../src/main/kotlin/com/infomaniak/core/auth/room/UserDao.kt | 4 ++++ .../core/ui/compose/contactcard/ContactCardViewModel.kt | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) 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 index aec2cd81e..08e080b04 100644 --- 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 @@ -100,8 +100,8 @@ class ContactCardViewModel( viewModelScope.launch { val card = current.editor.toCard(current.user.avatar) + accountUtils.updateUserCard(userId, card) val updatedUser = current.user.copy(card = card) - accountUtils.updateUser(updatedUser) currentUser = updatedUser _uiState.value = ContactCardUiState.Preview(user = updatedUser, card = card) } @@ -111,8 +111,8 @@ class ContactCardViewModel( val current = _uiState.value as? ContactCardUiState.Preview ?: return viewModelScope.launch { + accountUtils.updateUserCard(userId, null) val updatedUser = current.user.copy(card = null) - accountUtils.updateUser(updatedUser) currentUser = updatedUser _uiState.value = ContactCardUiState.Onboarding(updatedUser) } From 3401f8e8814445fddcbfbcf5066c5cc8e81c4fc8 Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Mon, 3 Aug 2026 16:35:07 +0200 Subject: [PATCH 08/12] refactor: Remove useless val --- .../core/ui/compose/contactcard/ContactCardViewModel.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 index 08e080b04..eeb6b44f6 100644 --- 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 @@ -99,7 +99,7 @@ class ContactCardViewModel( val current = _uiState.value as? ContactCardUiState.Editing ?: return viewModelScope.launch { - val card = current.editor.toCard(current.user.avatar) + val card = current.editor.toCard() accountUtils.updateUserCard(userId, card) val updatedUser = current.user.copy(card = card) currentUser = updatedUser @@ -149,7 +149,7 @@ data class ContactCardEditorState( val website: String, val additionalUrls: List, ) { - fun toCard(fallbackAvatarUrl: String?): Card { + 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)) } @@ -167,7 +167,7 @@ data class ContactCardEditorState( email = email.trim(), phone = phone.trim(), company = company.trim().takeIf(String::isNotBlank), - avatarUrl = avatarUrl?.takeIf(String::isNotBlank) ?: fallbackAvatarUrl, + avatarUrl = avatarUrl?.takeIf(String::isNotBlank), links = links, ) } From ee1a2ed2e70c3cebcb1a10c8fb8e39d18db51048 Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Mon, 3 Aug 2026 16:47:58 +0200 Subject: [PATCH 09/12] feat: Add content description for accessibility --- .../core/ui/compose/contactcard/component/LinksRow.kt | 3 ++- Ui/Compose/ContactCard/src/main/res/values-da/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values-de/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values-el/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values-es/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values-fi/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values-fr/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values-it/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values-nb/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values-nl/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values-pl/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values-pt/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values-sv/strings.xml | 1 + Ui/Compose/ContactCard/src/main/res/values/strings.xml | 1 + 14 files changed, 15 insertions(+), 1 deletion(-) 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 index 2e181d248..fb0d5b5aa 100644 --- 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 @@ -28,6 +28,7 @@ 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 @@ -49,7 +50,7 @@ internal fun LinksRow(links: List, modifier: Modifier = Modifier) { val linksOfType = grouped[type] ?: return@forEach Icon( imageVector = ImageVector.vectorResource(type.iconRes()), - contentDescription = null, + contentDescription = stringResource(R.string.socialNetworksIconContentDescription), tint = MaterialTheme.colorScheme.primary, modifier = Modifier.size(22.dp), ) 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 From 94373505b5539a3dd975ea17f04533605ba2b86f Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Mon, 3 Aug 2026 16:57:37 +0200 Subject: [PATCH 10/12] refactor: Create validation function to ensure a single source of truth --- .../contactcard/ContactCardViewModel.kt | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) 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 index eeb6b44f6..0568e3558 100644 --- 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 @@ -60,19 +60,23 @@ class ContactCardViewModel( fun startCreate() { val user = currentUser ?: return + val editor = ContactCardEditorState.fromUser(user) _uiState.value = ContactCardUiState.Editing( user = user, - editor = ContactCardEditorState.fromUser(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 = ContactCardEditorState.fromCard(card, user.avatar), + editor = editor, existingCard = card, + isValid = editor.validate(), ) } @@ -82,7 +86,7 @@ class ContactCardViewModel( fun updateDraft(editor: ContactCardEditorState) { val current = _uiState.value as? ContactCardUiState.Editing ?: return - _uiState.value = current.copy(editor = editor) + _uiState.value = current.copy(editor = editor, isValid = editor.validate()) } fun addAdditionalUrl() { @@ -98,6 +102,8 @@ class ContactCardViewModel( 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) @@ -132,7 +138,12 @@ sealed interface 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?) : ContactCardUiState + data class Editing( + val user: User, + val editor: ContactCardEditorState, + val existingCard: Card?, + val isValid: Boolean = false, + ) : ContactCardUiState } data class ContactCardEditorState( @@ -149,6 +160,13 @@ data class ContactCardEditorState( 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)) } From 29847ac2c8b3838ae3d746b04f73f6a5b97c5c7f Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Fri, 14 Aug 2026 10:05:00 +0200 Subject: [PATCH 11/12] fix: Update preview --- .../compose/contactcard/component/ContactVCardBloc.kt | 9 ++++++--- .../ui/compose/contactcard/component/QrCodeHeader.kt | 10 +++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) 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 index c91056002..23cd8175c 100644 --- 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 @@ -30,6 +30,7 @@ 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 androidx.compose.ui.tooling.preview.PreviewParameter import com.infomaniak.core.auth.models.user.Card import com.infomaniak.core.auth.models.user.CardLinkType import com.infomaniak.core.auth.models.user.User @@ -93,12 +94,14 @@ internal fun ContactVCardBloc(user: User, card: Card) { @Preview(name = "ContactVCardBloc") @Composable -private fun ContactVCardBlocPreview() { +private fun ContactVCardBlocPreview( + @PreviewParameter(ContactPreviewProvider::class) contactData: PreviewContactData, +) { MaterialTheme { Surface { ContactVCardBloc( - user = previewUser(), - card = previewCard(), + user = contactData.user, + card = contactData.card, ) } } 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 index a9230ec2e..4bfa24a90 100644 --- 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 @@ -38,6 +38,7 @@ 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.tooling.preview.PreviewParameter import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.infomaniak.core.auth.models.user.Card @@ -48,6 +49,7 @@ 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( @@ -143,12 +145,14 @@ private fun QrCodeAvatar(user: User, qrSize: Dp) { @Preview(name = "QrCodeHeader") @Composable -private fun QrCodeHeaderPreview() { +private fun QrCodeHeaderPreview( + @PreviewParameter(ContactPreviewProvider::class) contactData: PreviewContactData, +) { MaterialTheme { Surface { QrCodeHeader( - user = previewUser(), - card = previewCard(), + user = contactData.user, + card = contactData.card, ) } } From bb89b8a096043da2a70f37361a1faf1a29a5bbb5 Mon Sep 17 00:00:00 2001 From: Elouan BOITEUX Date: Fri, 14 Aug 2026 10:24:46 +0200 Subject: [PATCH 12/12] refactor: Clean code --- .../contactcard/ContactCardViewModel.kt | 4 ++-- .../contactcard/component/QrCodeHeader.kt | 18 +++++++----------- 2 files changed, 9 insertions(+), 13 deletions(-) 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 index 0568e3558..da2cf752f 100644 --- 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 @@ -48,7 +48,7 @@ class ContactCardViewModel( loadUser() } - fun loadUser() { + private fun loadUser() { viewModelScope.launch { val user = accountUtils.getUserById(userId) currentUser = user @@ -129,7 +129,7 @@ class ContactCardViewModel( } companion object { - const val USER_ID_KEY = "userId" + private const val USER_ID_KEY = "userId" } } 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 index 4bfa24a90..2c942ee50 100644 --- 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 @@ -129,17 +129,13 @@ private fun QrCodeAvatar(user: User, qrSize: Dp) { .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), - ) - } + Avatar( + avatarType = AvatarType.fromUser(user), + modifier = Modifier + .padding(3.dp) + .fillMaxSize() + .clip(CircleShape), + ) } }