diff --git a/jami-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt b/jami-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt
index 78c82c4541ff7d4d4dc662ab985c9901ccc6f1a9..344e7280ce1c457ffdd042c48bfdd5c96cf6aed0 100644
--- a/jami-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt
+++ b/jami-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.kt
@@ -84,7 +84,6 @@ import net.jami.model.Account.ComposingStatus
 import net.jami.model.Interaction.InteractionStatus
 import net.jami.utils.StringUtils
 import java.io.File
-import java.lang.ref.WeakReference
 import java.text.DateFormat
 import java.util.*
 import java.util.concurrent.TimeUnit
@@ -743,12 +742,24 @@ class ConversationAdapter(
         player.seekTo(1)
     }
 
-    private fun openItemMenu(cvh: ConversationViewHolder, v: View, interaction: Interaction) {
-        MenuConversationBinding.inflate(LayoutInflater.from(v.context)).apply {
+    /**
+     * Display and manage the popup that allows user to react/reply/share/edit/delete message ...
+     *
+     * @param conversationViewHolder the view layout.
+     * @param view
+     * @param interaction
+     */
+    private fun openItemMenu(
+        conversationViewHolder: ConversationViewHolder, view: View, interaction: Interaction
+    ) {
+
+        // Inflate design from XML.
+        MenuConversationBinding.inflate(LayoutInflater.from(view.context)).apply {
             val history = interaction.historyObservable.blockingFirst()
             val lastElement = history.last()
             val isDeleted = lastElement is TextMessage && lastElement.body.isNullOrEmpty()
-            Log.w(TAG, "isDeleted $isDeleted ${history.size}")
+
+            // Configure what should be displayed
             convActionOpenText.isVisible = interaction is DataTransfer && interaction.isComplete
             convActionDownloadText.isVisible = interaction is DataTransfer && interaction.isComplete
             convActionCopyText.isVisible = !isDeleted
@@ -756,77 +767,141 @@ class ConversationAdapter(
             convActionDelete.isVisible = !isDeleted && !interaction.isIncoming
             convActionHistory.isVisible = !isDeleted && history.size > 1
             root.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
-            val popupWindow = WeakReference(PopupWindow(root, LinearLayout.LayoutParams.WRAP_CONTENT, root.measuredHeight, true).apply {
-                setOnDismissListener {
-                    if (convColor != 0
-                        && interaction.type == Interaction.InteractionType.TEXT
-                        && !interaction.isIncoming
-                    ) {
-                        v.background?.setTint(convColor)
-                    } else {
-                        v.background?.setTintList(null)
+
+            // The popup that display all the buttons
+            val popupWindow = PopupWindow(
+                root, LinearLayout.LayoutParams.WRAP_CONTENT, root.measuredHeight, true
+            )
+                .apply {
+                    elevation = view.context.resources.getDimension(R.dimen.call_preview_elevation)
+                    showAsDropDown(view)
+                }
+
+            val textViews = listOf(
+                convActionEmoji1.chip, convActionEmoji2.chip,
+                convActionEmoji3.chip, convActionEmoji4.chip
+            )
+
+            convActionEmoji1.chip.text = view.context.getString(R.string.default_emoji_1)
+            convActionEmoji2.chip.text = view.context.getString(R.string.default_emoji_2)
+            convActionEmoji3.chip.text = view.context.getString(R.string.default_emoji_3)
+            convActionEmoji4.chip.text = view.context.getString(R.string.default_emoji_4)
+
+            // Subscribe on reactions to allows user to see which reaction he already selected.
+            val disposable = interaction.reactionObservable
+                .observeOn(DeviceUtils.uiScheduler)
+                .subscribe { reactions ->
+                    textViews.forEach { textView ->
+                        // Set checked reactions already sent.
+                        textView.isChecked = reactions.any {
+                            (textView.text == it.body) && (it.contact?.isUser == true)
+                        }
                     }
+                    popupWindow.update()
                 }
-                elevation = v.context.resources.getDimension(R.dimen.call_preview_elevation)
-                showAsDropDown(v)
-            })
+            conversationViewHolder.compositeDisposable.add(disposable)
+
+            popupWindow.setOnDismissListener {
+                if (convColor != 0
+                    && interaction.type == Interaction.InteractionType.TEXT
+                    && !interaction.isIncoming
+                ) view.background?.setTint(convColor)
+                else view.background?.setTintList(null)
+                // Remove disposable.
+                conversationViewHolder.compositeDisposable.remove(disposable)
+            }
+
+            // Callback executed when emoji is clicked.
+            // We want to know if the reaction is already set.
+            // If set we want to remove, else we want to append.
             val emojiCallback = View.OnClickListener { view ->
-                presenter.sendReaction(interaction, (view as TextView).text)
-                popupWindow.get()?.dismiss()
+                // Subscribe to know which are the current reactions.
+                conversationViewHolder.compositeDisposable.add(interaction.reactionObservable
+                    .observeOn(DeviceUtils.uiScheduler)
+                    .firstOrError()
+                    .subscribe { reactions ->
+                        // Try to find a reaction having corresponding to the one clicked.
+                        val reaction = reactions.firstOrNull {
+                            (it.body == (view as TextView).text) && (it.contact?.isUser == true)
+                        }
+                        if (reaction != null)
+                        // Previously, it was not forbidden to send multiple times the same
+                        // reaction. Hence, we only remove the first one.
+                            presenter.removeReaction(reaction)
+                        else // If null, it means we didn't find anything. So let's send it.
+                            presenter.sendReaction(interaction, (view as TextView).text)
+                        popupWindow.dismiss()
+                    }
+                )
             }
-            convActionEmoji1.setOnClickListener(emojiCallback)
-            convActionEmoji2.setOnClickListener(emojiCallback)
-            convActionEmoji3.setOnClickListener(emojiCallback)
-            convActionEmoji4.setOnClickListener(emojiCallback)
+            textViews.forEach { it.setOnClickListener(emojiCallback) } // Set callback
+
+            // Configure reply
             convActionReply.setOnClickListener {
                 presenter.startReplyTo(interaction)
-                popupWindow.get()?.dismiss()
+                popupWindow.dismiss()
             }
+
             convActionMore.setOnClickListener {
                 menuActions.isVisible = !menuActions.isVisible
             }
+
+            // Open file
             convActionOpenText.setOnClickListener {
                 presenter.openFile(interaction)
             }
+
+            // Save file
             convActionDownloadText.setOnClickListener {
                 presenter.saveFile(interaction)
             }
+
+            // Manage copy
             convActionCopyText.setOnClickListener {
                 addToClipboard(lastElement.body)
-                popupWindow.get()?.dismiss()
+                popupWindow.dismiss()
             }
+
+            // Manage Edit and Delete actions
             if (!interaction.isIncoming) {
+                // Edit
                 convActionEdit.setOnClickListener {
                     try {
                         val i = Intent(it.context, MessageEditActivity::class.java)
                             .setData(Uri.withAppendedPath(ConversationPath.toUri(interaction.account!!, interaction.conversationId!!), interaction.messageId))
                             .setAction(Intent.ACTION_EDIT)
-                            .putExtra(Intent.EXTRA_TEXT, cvh.mMsgTxt!!.text.toString())
-                        val options = ActivityOptionsCompat.makeSceneTransitionAnimation(conversationFragment.requireActivity(), cvh.mMsgTxt!!, "messageEdit")
+                            .putExtra(Intent.EXTRA_TEXT, conversationViewHolder.mMsgTxt!!.text.toString())
+                        val options = ActivityOptionsCompat.makeSceneTransitionAnimation(conversationFragment.requireActivity(), conversationViewHolder.mMsgTxt!!, "messageEdit")
                         conversationFragment.startActivityForResult(i, ConversationFragment.REQUEST_CODE_EDIT_MESSAGE, options.toBundle())
                     } catch (e: Exception) {
                         Log.w(TAG, "Can't open picture", e)
                     }
-                    popupWindow.get()?.dismiss()
+                    popupWindow.dismiss()
                 }
+
+                // Delete
                 convActionDelete.setOnClickListener {
                     presenter.deleteConversationItem(interaction)
-                    popupWindow.get()?.dismiss()
+                    popupWindow.dismiss()
                 }
             } else {
                 convActionEdit.setOnClickListener(null)
                 convActionDelete.setOnClickListener(null)
             }
+
+            // Share
             convActionShare.setOnClickListener {
                 if (interaction is DataTransfer)
                     presenter.shareFile(interaction)
                 else if (interaction is TextMessage)
                     presenter.shareText(interaction)
-                popupWindow.get()?.dismiss()
+                popupWindow.dismiss()
             }
+
+            // Message history
             if (convActionHistory.isVisible)
                 convActionHistory.setOnClickListener {
-                    cvh.compositeDisposable.add(
+                    conversationViewHolder.compositeDisposable.add(
                         interaction.historyObservable.firstOrError().subscribe { c ->
                             Log.w(TAG, "Message history ${c.size}")
                             c.forEach {
@@ -839,8 +914,9 @@ class ConversationAdapter(
                                 { dialog, which -> dialog.dismiss() }
                                 .create()
                                 .show()
-                        })
-                    popupWindow.get()?.dismiss()
+                        }
+                    )
+                    popupWindow.dismiss()
                 }
         }
     }
diff --git a/jami-android/app/src/main/res/layout/menu_conversation.xml b/jami-android/app/src/main/res/layout/menu_conversation.xml
index 8abfb1cd41a024785a0d75416cd20d198cc0d4bf..7ccd391a56aea2f437e94f84c977e4868aaaa694 100644
--- a/jami-android/app/src/main/res/layout/menu_conversation.xml
+++ b/jami-android/app/src/main/res/layout/menu_conversation.xml
@@ -15,49 +15,17 @@
         android:orientation="horizontal"
         android:padding="8dp">
 
-        <TextView
-            android:id="@+id/conv_action_emoji1"
-            android:layout_width="36dp"
-            android:layout_height="36dp"
-            android:background="?selectableItemBackgroundBorderless"
-            android:clickable="true"
-            android:focusable="true"
-            android:gravity="center"
-            android:text="@string/default_emoji_1"
-            android:textSize="18dp" />
+        <include layout="@layout/item_reaction_chip_18"
+            android:id="@+id/conv_action_emoji1"/>
 
-        <TextView
-            android:id="@+id/conv_action_emoji2"
-            android:layout_width="36dp"
-            android:layout_height="36dp"
-            android:background="?selectableItemBackgroundBorderless"
-            android:clickable="true"
-            android:focusable="true"
-            android:gravity="center"
-            android:text="@string/default_emoji_2"
-            android:textSize="18dp" />
+        <include layout="@layout/item_reaction_chip_18"
+            android:id="@+id/conv_action_emoji2"/>
 
-        <TextView
-            android:id="@+id/conv_action_emoji3"
-            android:layout_width="36dp"
-            android:layout_height="36dp"
-            android:background="?selectableItemBackgroundBorderless"
-            android:clickable="true"
-            android:focusable="true"
-            android:gravity="center"
-            android:text="@string/default_emoji_3"
-            android:textSize="18dp" />
+        <include layout="@layout/item_reaction_chip_18"
+            android:id="@+id/conv_action_emoji3"/>
 
-        <TextView
-            android:id="@+id/conv_action_emoji4"
-            android:layout_width="36dp"
-            android:layout_height="36dp"
-            android:background="?selectableItemBackgroundBorderless"
-            android:clickable="true"
-            android:focusable="true"
-            android:gravity="center"
-            android:text="@string/default_emoji_5"
-            android:textSize="18dp" />
+        <include layout="@layout/item_reaction_chip_18"
+            android:id="@+id/conv_action_emoji4"/>
 
         <ImageView
             android:visibility="gone"