Skip to content

Commit

Permalink
Merge pull request #1913 from Infomaniak/bimi
Browse files Browse the repository at this point in the history
Add Bimi feature
  • Loading branch information
NicolasBourdin88 authored Jun 25, 2024
2 parents 9ed29a8 + 8e70b75 commit c07bf5a
Show file tree
Hide file tree
Showing 26 changed files with 267 additions and 44 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ dependencies {

implementation libs.sentry.android.fragment

implementation libs.coil.svg

// Test
testImplementation libs.junit
androidTestImplementation libs.ext.junit
Expand Down
9 changes: 9 additions & 0 deletions app/src/main/java/com/infomaniak/mail/MainApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/com/infomaniak/mail/data/api/ApiRoutes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,8 @@ object ApiRoutes {
fun resource(resource: String): String {
return "$MAIL_API$resource"
}

fun bimi(bimi: String): String {
return "$MAIL_API$bimi"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -199,6 +200,7 @@ object RealmDatabase {
CalendarEvent::class,
Attendee::class,
Signature::class,
Bimi::class,
)
//endregion

Expand Down
64 changes: 64 additions & 0 deletions app/src/main/java/com/infomaniak/mail/data/models/Bimi.kt
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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<Bimi> {

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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -213,7 +214,7 @@ class Thread : RealmObject {
}
}

fun computeAvatarRecipient(): Recipient? = runCatching {
fun computeAvatarRecipient(): Pair<Recipient?, Bimi?> = runCatching {

val message = messages
.lastOrNull { it.folder.role != FolderRole.SENT && it.folder.role != FolderRole.DRAFT }
Expand All @@ -224,7 +225,7 @@ class Thread : RealmObject {
else -> message.from
}

recipients.firstOrNull()
recipients.firstOrNull() to message.bimi

}.getOrElse { throwable ->
Sentry.withScope { scope ->
Expand All @@ -239,7 +240,7 @@ class Thread : RealmObject {
Sentry.captureException(throwable)
}

null
null to null
}

fun computeDisplayedRecipients(): RealmList<Recipient> = when (folder.role) {
Expand Down
5 changes: 5 additions & 0 deletions app/src/main/java/com/infomaniak/mail/di/ApplicationModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -74,4 +75,8 @@ object ApplicationModule {
appContext: Context,
workManager: WorkManager,
): AppUpdateScheduler = AppUpdateScheduler(appContext, workManager)

@Provides
@Singleton
fun providesSvgImageLoader(mainApplication: MainApplication): ImageLoader = mainApplication.createSvgImageLoader()
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Recipient>): String {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<DetailedRecipientViewHolder>() {

private var recipients = emptyList<Recipient>()

private var bimi: Bimi? = null

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailedRecipientViewHolder {
return DetailedRecipientViewHolder(ItemDetailedContactBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
Expand All @@ -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<Recipient>) {
fun updateList(newList: List<Recipient>, newBimi: Bimi? = null) {
recipients = newList
bimi = newBimi
}

class DetailedRecipientViewHolder(val binding: ItemDetailedContactBinding) : ViewHolder(binding.root)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 ->
Expand Down
Loading

0 comments on commit c07bf5a

Please sign in to comment.