diff --git a/app/build.gradle b/app/build.gradle index 948f8432e7..c33cb42bab 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -113,6 +113,8 @@ dependencies { implementation libs.sentry.android.fragment + implementation libs.coil.svg + // Test testImplementation libs.junit androidTestImplementation libs.ext.junit diff --git a/app/src/main/java/com/infomaniak/mail/MainApplication.kt b/app/src/main/java/com/infomaniak/mail/MainApplication.kt index 3450bff5bd..b324c18551 100644 --- a/app/src/main/java/com/infomaniak/mail/MainApplication.kt +++ b/app/src/main/java/com/infomaniak/mail/MainApplication.kt @@ -33,6 +33,7 @@ import androidx.lifecycle.lifecycleScope import androidx.work.Configuration import coil.ImageLoader import coil.ImageLoaderFactory +import coil.decode.SvgDecoder import com.facebook.stetho.Stetho import com.infomaniak.lib.core.InfomaniakCore import com.infomaniak.lib.core.auth.TokenInterceptorListener @@ -274,6 +275,14 @@ open class MainApplication : Application(), ImageLoaderFactory, DefaultLifecycle lastAppClosingTime = null } + fun createSvgImageLoader(): ImageLoader { + return CoilUtils.newImageLoader( + applicationContext, + tokenInterceptorListener(), + customFactories = listOf(SvgDecoder.Factory()) + ) + } + companion object { private const val FIRST_LAUNCH_TIME = 0L } diff --git a/app/src/main/java/com/infomaniak/mail/data/api/ApiRoutes.kt b/app/src/main/java/com/infomaniak/mail/data/api/ApiRoutes.kt index 718a47d769..82b523508b 100644 --- a/app/src/main/java/com/infomaniak/mail/data/api/ApiRoutes.kt +++ b/app/src/main/java/com/infomaniak/mail/data/api/ApiRoutes.kt @@ -261,4 +261,8 @@ object ApiRoutes { fun resource(resource: String): String { return "$MAIL_API$resource" } + + fun bimi(bimi: String): String { + return "$MAIL_API$bimi" + } } diff --git a/app/src/main/java/com/infomaniak/mail/data/cache/RealmDatabase.kt b/app/src/main/java/com/infomaniak/mail/data/cache/RealmDatabase.kt index 93d7ce7cbc..499216453a 100644 --- a/app/src/main/java/com/infomaniak/mail/data/cache/RealmDatabase.kt +++ b/app/src/main/java/com/infomaniak/mail/data/cache/RealmDatabase.kt @@ -22,6 +22,7 @@ import com.infomaniak.mail.data.models.AppSettings import com.infomaniak.mail.data.models.Attachment import com.infomaniak.mail.data.models.Folder import com.infomaniak.mail.data.models.Quotas +import com.infomaniak.mail.data.models.Bimi import com.infomaniak.mail.data.models.addressBook.AddressBook import com.infomaniak.mail.data.models.calendar.Attendee import com.infomaniak.mail.data.models.calendar.CalendarEvent @@ -199,6 +200,7 @@ object RealmDatabase { CalendarEvent::class, Attendee::class, Signature::class, + Bimi::class, ) //endregion diff --git a/app/src/main/java/com/infomaniak/mail/data/models/Bimi.kt b/app/src/main/java/com/infomaniak/mail/data/models/Bimi.kt new file mode 100644 index 0000000000..8b318e0571 --- /dev/null +++ b/app/src/main/java/com/infomaniak/mail/data/models/Bimi.kt @@ -0,0 +1,64 @@ +/* + * Infomaniak Mail - Android + * Copyright (C) 2024 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 version 3 of the License, or + * (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.mail.data.models + +import android.os.Parcel +import android.os.Parcelable +import io.realm.kotlin.types.EmbeddedRealmObject +import kotlinx.parcelize.Parceler +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Parcelize +@Serializable +class Bimi() : EmbeddedRealmObject, Parcelable { + + //region Remote data + @SerialName("svg_content") + var svgContentUrl: String? = null + @SerialName("is_certified") + var isCertified: Boolean = false + //endregion + + constructor(svgContentUrl: String, isCertified: Boolean) : this() { + this.svgContentUrl = svgContentUrl + this.isCertified = isCertified + } + + companion object : Parceler { + + override fun create(parcel: Parcel): Bimi = with(parcel) { + val svgContentUrl = readString()!! + val isCertified = customReadBoolean() + + return Bimi(svgContentUrl, isCertified) + } + + override fun Bimi.write(parcel: Parcel, flags: Int) = with(parcel) { + writeString(svgContentUrl) + customWriteBoolean(isCertified) + } + + private fun Parcel.customWriteBoolean(value: Boolean) { + writeInt(if (value) 1 else 0) + } + + private fun Parcel.customReadBoolean(): Boolean = readInt() != 0 + } +} diff --git a/app/src/main/java/com/infomaniak/mail/data/models/message/Message.kt b/app/src/main/java/com/infomaniak/mail/data/models/message/Message.kt index b405a3efb3..7376906281 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/message/Message.kt +++ b/app/src/main/java/com/infomaniak/mail/data/models/message/Message.kt @@ -24,6 +24,7 @@ import com.infomaniak.mail.data.api.RealmInstantSerializer import com.infomaniak.mail.data.api.UnwrappingJsonListSerializer import com.infomaniak.mail.data.cache.mailboxContent.FolderController.Companion.SEARCH_FOLDER_ID import com.infomaniak.mail.data.models.Attachment +import com.infomaniak.mail.data.models.Bimi import com.infomaniak.mail.data.models.Folder import com.infomaniak.mail.data.models.calendar.CalendarEventResponse import com.infomaniak.mail.data.models.correspondent.Recipient @@ -97,6 +98,7 @@ class Message : RealmObject { var size: Int = 0 @SerialName("has_unsubscribe_link") var hasUnsubscribeLink: Boolean? = null + var bimi : Bimi? = null // TODO: Those are unused for now, but if we ever want to use them, we need to save them in `Message.keepHeavyData()`. // If we don't do it now, we'll probably forget to do it in the future. diff --git a/app/src/main/java/com/infomaniak/mail/data/models/thread/Thread.kt b/app/src/main/java/com/infomaniak/mail/data/models/thread/Thread.kt index ca3d2ae642..93122866aa 100644 --- a/app/src/main/java/com/infomaniak/mail/data/models/thread/Thread.kt +++ b/app/src/main/java/com/infomaniak/mail/data/models/thread/Thread.kt @@ -26,6 +26,7 @@ import com.infomaniak.mail.MatomoMail.SEARCH_FOLDER_FILTER_NAME import com.infomaniak.mail.R import com.infomaniak.mail.data.api.RealmInstantSerializer import com.infomaniak.mail.data.cache.mailboxContent.FolderController +import com.infomaniak.mail.data.models.Bimi import com.infomaniak.mail.data.models.Folder import com.infomaniak.mail.data.models.Folder.FolderRole import com.infomaniak.mail.data.models.correspondent.Recipient @@ -213,7 +214,7 @@ class Thread : RealmObject { } } - fun computeAvatarRecipient(): Recipient? = runCatching { + fun computeAvatarRecipient(): Pair = runCatching { val message = messages .lastOrNull { it.folder.role != FolderRole.SENT && it.folder.role != FolderRole.DRAFT } @@ -224,7 +225,7 @@ class Thread : RealmObject { else -> message.from } - recipients.firstOrNull() + recipients.firstOrNull() to message.bimi }.getOrElse { throwable -> Sentry.withScope { scope -> @@ -239,7 +240,7 @@ class Thread : RealmObject { Sentry.captureException(throwable) } - null + null to null } fun computeDisplayedRecipients(): RealmList = when (folder.role) { diff --git a/app/src/main/java/com/infomaniak/mail/di/ApplicationModule.kt b/app/src/main/java/com/infomaniak/mail/di/ApplicationModule.kt index 7306e3dda2..0198213e0d 100644 --- a/app/src/main/java/com/infomaniak/mail/di/ApplicationModule.kt +++ b/app/src/main/java/com/infomaniak/mail/di/ApplicationModule.kt @@ -21,6 +21,7 @@ import android.app.Application import android.content.Context import androidx.core.app.NotificationManagerCompat import androidx.work.WorkManager +import coil.ImageLoader import com.infomaniak.lib.stores.AppUpdateScheduler import com.infomaniak.lib.stores.StoresSettingsRepository import com.infomaniak.mail.MainApplication @@ -74,4 +75,8 @@ object ApplicationModule { appContext: Context, workManager: WorkManager, ): AppUpdateScheduler = AppUpdateScheduler(appContext, workManager) + + @Provides + @Singleton + fun providesSvgImageLoader(mainApplication: MainApplication): ImageLoader = mainApplication.createSvgImageLoader() } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/AvatarNameEmailView.kt b/app/src/main/java/com/infomaniak/mail/ui/main/AvatarNameEmailView.kt index 7e461e6292..c2a3b26839 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/AvatarNameEmailView.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/AvatarNameEmailView.kt @@ -31,6 +31,7 @@ import androidx.core.view.isVisible import com.infomaniak.lib.core.utils.getAttributes import com.infomaniak.lib.core.utils.setMarginsRelative import com.infomaniak.mail.R +import com.infomaniak.mail.data.models.Bimi import com.infomaniak.mail.data.models.calendar.Attendee import com.infomaniak.mail.data.models.correspondent.Correspondent import com.infomaniak.mail.data.models.correspondent.MergedContact @@ -71,9 +72,9 @@ class AvatarNameEmailView @JvmOverloads constructor( } } - fun setCorrespondent(correspondent: Correspondent) = with(binding) { - userAvatar.loadAvatar(correspondent) - setNameAndEmail(correspondent) + fun setCorrespondent(correspondent: Correspondent, bimi: Bimi? = null) = with(binding) { + userAvatar.loadAvatar(correspondent, bimi) + setNameAndEmail(correspondent, isCorrespondentCertified = bimi?.isCertified ?: false) } fun setMergedContact(mergedContact: MergedContact) = with(binding) { @@ -86,12 +87,17 @@ class AvatarNameEmailView @JvmOverloads constructor( setNameAndEmail(attendee) } - private fun ViewAvatarNameEmailBinding.setNameAndEmail(correspondent: Correspondent) { + private fun ViewAvatarNameEmailBinding.setNameAndEmail( + correspondent: Correspondent, + isCorrespondentCertified: Boolean = false, + ) { val filledSingleField = fillInUserNameAndEmail(correspondent, userName, userEmail, ignoreIsMe = !processNameAndEmail) if (displayAsAttendee) { val userNameTextColor = if (filledSingleField) R.style.AvatarNameEmailSecondary else R.style.AvatarNameEmailPrimary userName.setTextAppearance(userNameTextColor) } + + iconCertified.isVisible = isCorrespondentCertified } fun setAutocompleteUnknownContact(searchQuery: String) = with(binding) { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt index 15aba5c08b..10670ab811 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/folder/ThreadListAdapter.kt @@ -49,6 +49,7 @@ import com.infomaniak.mail.R import com.infomaniak.mail.data.LocalSettings import com.infomaniak.mail.data.LocalSettings.SwipeAction import com.infomaniak.mail.data.LocalSettings.ThreadDensity +import com.infomaniak.mail.data.api.ApiRoutes import com.infomaniak.mail.data.models.Folder.FolderRole import com.infomaniak.mail.data.models.correspondent.Recipient import com.infomaniak.mail.data.models.thread.Thread @@ -400,7 +401,12 @@ class ThreadListAdapter @Inject constructor( } private fun CardviewThreadItemBinding.displayAvatar(thread: Thread) { - expeditorAvatar.loadAvatar(thread.computeAvatarRecipient()) + val (recipient, bimi) = thread.computeAvatarRecipient() + if (bimi?.isCertified == true) { + expeditorAvatar.loadBimiAvatar(ApiRoutes.bimi(bimi.svgContentUrl.toString()), recipient) + } else { + expeditorAvatar.loadAvatar(recipient) + } } private fun CardviewThreadItemBinding.formatRecipientNames(recipients: List): String { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/DetailedContactBottomSheetDialog.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/DetailedContactBottomSheetDialog.kt index 58a02ccdfa..f25eed1182 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/DetailedContactBottomSheetDialog.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/DetailedContactBottomSheetDialog.kt @@ -21,6 +21,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.navigation.fragment.navArgs import com.infomaniak.lib.core.utils.safeBinding @@ -53,7 +54,11 @@ class DetailedContactBottomSheetDialog : ActionsBottomSheetDialog() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) = with(binding) { super.onViewCreated(view, savedInstanceState) - contactDetails.setCorrespondent(navigationArgs.recipient) + + val bimi = navigationArgs.bimi + containerInfoCertified.isVisible = bimi?.isCertified == true + contactDetails.setCorrespondent(navigationArgs.recipient, bimi) + setupListeners() } diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/DetailedRecipientAdapter.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/DetailedRecipientAdapter.kt index 7b74f47b97..51862e7d18 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/DetailedRecipientAdapter.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/DetailedRecipientAdapter.kt @@ -23,17 +23,20 @@ import androidx.recyclerview.widget.RecyclerView.Adapter import androidx.recyclerview.widget.RecyclerView.ViewHolder import com.infomaniak.lib.core.utils.context import com.infomaniak.mail.MatomoMail.trackMessageEvent +import com.infomaniak.mail.data.models.Bimi import com.infomaniak.mail.data.models.correspondent.Recipient import com.infomaniak.mail.databinding.ItemDetailedContactBinding import com.infomaniak.mail.ui.main.thread.DetailedRecipientAdapter.DetailedRecipientViewHolder import com.infomaniak.mail.utils.UiUtils.fillInUserNameAndEmail class DetailedRecipientAdapter( - private val onContactClicked: ((contact: Recipient) -> Unit)?, + private val onContactClicked: ((contact: Recipient, bimi: Bimi?) -> Unit)?, ) : Adapter() { private var recipients = emptyList() + private var bimi: Bimi? = null + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailedRecipientViewHolder { return DetailedRecipientViewHolder(ItemDetailedContactBinding.inflate(LayoutInflater.from(parent.context), parent, false)) } @@ -45,14 +48,15 @@ class DetailedRecipientAdapter( name.setOnClickListener { context.trackMessageEvent("selectRecipient") - onContactClicked?.invoke(recipient) + onContactClicked?.invoke(recipient, bimi) } } override fun getItemCount(): Int = recipients.count() - fun updateList(newList: List) { + fun updateList(newList: List, newBimi: Bimi? = null) { recipients = newList + bimi = newBimi } class DetailedRecipientViewHolder(val binding: ItemDetailedContactBinding) : ViewHolder(binding.root) diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadAdapter.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadAdapter.kt index 79062ef81e..3a5678129d 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadAdapter.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadAdapter.kt @@ -41,6 +41,7 @@ import com.infomaniak.lib.core.utils.isNightModeEnabled import com.infomaniak.mail.MatomoMail.trackMessageEvent import com.infomaniak.mail.R import com.infomaniak.mail.data.models.Attachment +import com.infomaniak.mail.data.models.Bimi import com.infomaniak.mail.data.models.calendar.Attendee import com.infomaniak.mail.data.models.calendar.Attendee.AttendanceState import com.infomaniak.mail.data.models.correspondent.Recipient @@ -337,19 +338,23 @@ class ThreadAdapter( shortMessageDate.text = "" } else { val firstSender = message.sender - userAvatar.loadAvatar(firstSender) + expeditorName.apply { text = firstSender?.let { context.getPrettyNameAndEmail(it).first } ?: run { context.getString(R.string.unknownRecipientTitle) } setTextAppearance(R.style.BodyMedium) } + + userAvatar.loadAvatar(firstSender, message.bimi) + iconCertified.isVisible = message.bimi?.isCertified ?: false + shortMessageDate.text = mailFormattedDate(context, messageDate) } val listener: OnClickListener? = message.sender?.let { recipient -> OnClickListener { context.trackMessageEvent("selectAvatar") - threadAdapterCallbacks?.onContactClicked?.invoke(recipient) + threadAdapterCallbacks?.onContactClicked?.invoke(recipient, message.bimi) } } @@ -397,7 +402,7 @@ class ThreadAdapter( private fun MessageViewHolder.bindRecipientDetails(message: Message, messageDate: Date) = with(binding) { - fromAdapter.updateList(message.from.toList()) + fromAdapter.updateList(message.from.toList(), message.bimi) toAdapter.updateList(message.to.toList()) val ccIsNotEmpty = message.cc.isNotEmpty() @@ -670,7 +675,7 @@ class ThreadAdapter( data class ThreadAdapterCallbacks( var onBodyWebViewFinishedLoading: (() -> Unit)? = null, - var onContactClicked: ((contact: Recipient) -> Unit)? = null, + var onContactClicked: ((contact: Recipient, bimi: Bimi?) -> Unit)? = null, var onDeleteDraftClicked: ((message: Message) -> Unit)? = null, var onDraftClicked: ((message: Message) -> Unit)? = null, var onAttachmentClicked: ((attachment: Attachment) -> Unit)? = null, @@ -708,7 +713,7 @@ class ThreadAdapter( private class MessageViewHolder( override val binding: ItemMessageBinding, private val shouldLoadDistantResources: Boolean, - onContactClicked: ((contact: Recipient) -> Unit)?, + onContactClicked: ((contact: Recipient, bimi: Bimi?) -> Unit)?, onAttachmentClicked: ((attachment: Attachment) -> Unit)?, onAttachmentOptionsClicked: ((attachment: Attachment) -> Unit)?, ) : ThreadAdapterViewHolder(binding) { diff --git a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt index d3e5c45e7c..7283fee300 100644 --- a/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt +++ b/app/src/main/java/com/infomaniak/mail/ui/main/thread/ThreadFragment.kt @@ -224,10 +224,10 @@ class ThreadFragment : Fragment() { override val isCalendarEventExpandedMap by threadState::isCalendarEventExpandedMap }, threadAdapterCallbacks = ThreadAdapterCallbacks( - onContactClicked = { + onContactClicked = { recipient, bimi -> safeNavigate( resId = R.id.detailedContactBottomSheetDialog, - args = DetailedContactBottomSheetDialogArgs(it).toBundle(), + args = DetailedContactBottomSheetDialogArgs(recipient, bimi).toBundle(), ) }, onDraftClicked = { message -> diff --git a/app/src/main/java/com/infomaniak/mail/views/AvatarView.kt b/app/src/main/java/com/infomaniak/mail/views/AvatarView.kt index fc36215830..814e5d12e4 100644 --- a/app/src/main/java/com/infomaniak/mail/views/AvatarView.kt +++ b/app/src/main/java/com/infomaniak/mail/views/AvatarView.kt @@ -25,6 +25,7 @@ import android.view.LayoutInflater import android.widget.FrameLayout import android.widget.ImageView import androidx.lifecycle.Observer +import coil.ImageLoader import coil.imageLoader import coil.load import com.infomaniak.lib.core.models.user.User @@ -33,6 +34,8 @@ import com.infomaniak.lib.core.utils.UtilsUi.getBackgroundColorBasedOnId import com.infomaniak.lib.core.utils.getAttributes import com.infomaniak.lib.core.utils.loadAvatar import com.infomaniak.mail.R +import com.infomaniak.mail.data.api.ApiRoutes +import com.infomaniak.mail.data.models.Bimi import com.infomaniak.mail.data.models.correspondent.Correspondent import com.infomaniak.mail.data.models.correspondent.MergedContact import com.infomaniak.mail.databinding.ViewAvatarBinding @@ -55,14 +58,20 @@ class AvatarView @JvmOverloads constructor( private val binding by lazy { ViewAvatarBinding.inflate(LayoutInflater.from(context), this, true) } private var currentCorrespondent: Correspondent? = null + private var isBimiShown: Boolean = false private val mergedContactObserver = Observer { contacts -> - currentCorrespondent?.let { correspondent -> loadAvatarUsingDictionary(correspondent, contacts) } + currentCorrespondent?.let { correspondent -> + if (!isBimiShown) loadAvatarUsingDictionary(correspondent, contacts) + } } @Inject lateinit var avatarMergedContactData: AvatarMergedContactData + @Inject + lateinit var svgImageLoader: ImageLoader + var strokeWidth: Float get() = binding.avatarImage.strokeWidth set(value) { @@ -140,6 +149,21 @@ class AvatarView @JvmOverloads constructor( binding.avatarImage.load(R.drawable.ic_unknown_user_avatar) } + fun loadBimiAvatar(bimiUrl: String, correspondent: Correspondent?) = with(binding.avatarImage) { + contentDescription = correspondent?.email.orEmpty() + isBimiShown = bimiUrl.isNotEmpty() + loadAvatar( + backgroundColor = context.getBackgroundColorBasedOnId( + correspondent?.email.orEmpty().hashCode(), + R.array.AvatarColors, + ), + avatarUrl = bimiUrl, + initials = correspondent?.initials.orEmpty(), + imageLoader = svgImageLoader, + initialsColor = context.getColor(R.color.onColorfulBackground), + ) + } + fun setImageDrawable(drawable: Drawable?) = binding.avatarImage.setImageDrawable(drawable) private fun searchInMergedContact(correspondent: Correspondent, contacts: MergedContactDictionary): MergedContact? { @@ -167,4 +191,13 @@ class AvatarView @JvmOverloads constructor( ) } } + + fun loadAvatar(correspondent: Correspondent?, bimi: Bimi?) { + val svgContentUrl = bimi?.svgContentUrl + if (bimi == null || !bimi.isCertified || svgContentUrl.isNullOrEmpty()) { + loadAvatar(correspondent) + } else { + loadBimiAvatar(ApiRoutes.bimi(svgContentUrl), correspondent) + } + } } diff --git a/app/src/main/res/drawable/ic_certified.xml b/app/src/main/res/drawable/ic_certified.xml new file mode 100644 index 0000000000..3d0defd282 --- /dev/null +++ b/app/src/main/res/drawable/ic_certified.xml @@ -0,0 +1,27 @@ + + + + diff --git a/app/src/main/res/layout/bottom_sheet_detailed_contact.xml b/app/src/main/res/layout/bottom_sheet_detailed_contact.xml index c4185bdf1b..40e34788f5 100644 --- a/app/src/main/res/layout/bottom_sheet_detailed_contact.xml +++ b/app/src/main/res/layout/bottom_sheet_detailed_contact.xml @@ -31,11 +31,32 @@ tools:email="steph.guy@ik.me" tools:name="@tools:sample/full_names" /> + + + + + + + diff --git a/app/src/main/res/layout/item_message.xml b/app/src/main/res/layout/item_message.xml index 228489dcb6..1184dfcf74 100644 --- a/app/src/main/res/layout/item_message.xml +++ b/app/src/main/res/layout/item_message.xml @@ -62,27 +62,24 @@ android:ellipsize="end" android:lines="1" app:layout_constrainedWidth="true" - app:layout_constraintEnd_toStartOf="@id/shortMessageDate" + app:layout_constraintEnd_toStartOf="@id/iconCertified" app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toEndOf="@id/userAvatar" app:layout_constraintTop_toTopOf="parent" tools:text="@tools:sample/full_names" /> - + android:layout_marginStart="@dimen/marginStandardVerySmall" + android:src="@drawable/ic_certified" + app:layout_constraintBottom_toBottomOf="@+id/expeditorName" + app:layout_constraintEnd_toStartOf="@id/shortMessageDate" + app:layout_constraintStart_toEndOf="@+id/expeditorName" + app:layout_constraintTop_toTopOf="@+id/expeditorName" + tools:ignore="ContentDescription" /> + - + android:gravity="center_vertical" + android:orientation="horizontal" + tools:ignore="UseCompoundDrawables"> + + + + + + Ordner kann nicht erstellt werden Der Ordner kann nicht geleert werden Benutzer ist bereits synchronisiert + Die Herkunft dieses Absenders wurde authentifiziert. Ich habe verstanden Der Absender %s gehört nicht zu Ihrer Organisation oder Ihren Kontakten. Seien Sie vorsichtig, wenn Sie sensible Informationen weitergeben. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 86819b1ff1..057aa06c18 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -275,6 +275,7 @@ No se puede crear la carpeta Imposible vaciar la carpeta El usuario ya está sincronizado + Se ha autentificado el origen del remitente. Comprendo El remitente %s no forma parte de tu organización ni de tus contactos. Ten cuidado si compartes información sensible. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5ef758369d..e912018838 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -277,6 +277,7 @@ Impossible de créer le dossier Impossible de vider le dossier L’utilisateur est déjà synchronisé + La provenance de cet expéditeur a été authentifiée. J’ai compris L’expéditeur %s ne fait pas partie de votre organisation ni de vos contacts. Soyez vigilant si vous partagez des informations sensibles. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 99a2b72a2e..822e7db8db 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -275,6 +275,7 @@ Impossibile creare la cartella Impossibile svuotare la cartella L’utente è già sincronizzato + L’origine del mittente è stata autenticata. Capisco Il mittente %s non fa parte della vostra organizzazione o dei vostri contatti. Fate attenzione se state condividendo informazioni sensibili. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9736b0dcaf..2da13744db 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -281,6 +281,7 @@ Unable to create the folder Folder cannot be emptied User is already synchronized + The sender’s origin has been authenticated. I understand The sender %s is not part of your organization or your contacts. Be careful if you are sharing sensitive information. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 914a9b9a2e..822dd52f32 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,7 @@ sentryAndroidFragment = "7.10.0" webkit = "1.11.0" workConcurrentFutures = "1.2.0" workRuntimeKtx = "2.9.0" +coilSvg = "2.6.0" [libraries] dotsindicator = { module = "com.tbuonomo:dotsindicator", version.ref = "dotsindicator" } @@ -43,6 +44,7 @@ sentry-android-fragment = { module = "io.sentry:sentry-android-fragment", versio webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" } work-concurrent-futures = { module = "androidx.concurrent:concurrent-futures-ktx", version.ref = "workConcurrentFutures" } work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } +coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coilSvg" } # Unit tests ext-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" } junit = { module = "junit:junit", version.ref = "junit" }