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