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">
+
+
+
+
+
+