From adbfde94d67116912713fcf6ce4f47b423e1926f Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Tue, 4 Jun 2019 17:14:12 +0200
Subject: [PATCH 01/18] Fix / move read receipt on m.replace events

---
 .../features/home/room/detail/RoomDetailViewModel.kt        | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
index b02a71e8da..7a47cff0f6 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
@@ -350,6 +350,12 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
 
     private fun handleEventDisplayed(action: RoomDetailActions.EventDisplayed) {
         displayedEventsObservable.accept(action)
+        //We need to update this with the related m.replace also (to move read receipt)
+        action.event.annotations?.editSummary?.sourceEvents?.forEach {
+            room.getTimeLineEvent(it)?.let {event ->
+                displayedEventsObservable.accept(RoomDetailActions.EventDisplayed(event))
+            }
+        }
     }
 
     private fun handleLoadMore(action: RoomDetailActions.LoadMore) {

From 53c91dc0c28a0f72623c74e347c014f071254129 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Tue, 4 Jun 2019 17:14:52 +0200
Subject: [PATCH 02/18] Ignore server aggregation until API ready

---
 .../room/EventRelationsAggregationTask.kt     | 33 +++++++++++--------
 1 file changed, 19 insertions(+), 14 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt
index 4ff661e237..a66b65ab71 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt
@@ -44,6 +44,9 @@ internal interface EventRelationsAggregationTask : Task<EventRelationsAggregatio
  */
 internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarchy) : EventRelationsAggregationTask {
 
+    //OPT OUT serer aggregation until API mature enough
+    private val SHOULD_HANDLE_SERVER_AGREGGATION = false
+
     override fun execute(params: EventRelationsAggregationTask.Params): Try<Unit> {
         return monarchy.tryTransactionAsync { realm ->
             update(realm, params.events, params.userId)
@@ -155,20 +158,22 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
     }
 
     private fun handleInitialAggregatedRelations(event: Event, roomId: String, aggregation: AggregatedAnnotation, realm: Realm) {
-        aggregation.chunk?.forEach {
-            if (it.type == EventType.REACTION) {
-                val eventId = event.eventId ?: ""
-                val existing = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
-                if (existing == null) {
-                    val eventSummary = EventAnnotationsSummaryEntity.create(realm, eventId)
-                    eventSummary.roomId = roomId
-                    val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
-                    sum.key = it.key
-                    sum.firstTimestamp = event.originServerTs ?: 0 //TODO how to maintain order?
-                    sum.count = it.count
-                    eventSummary.reactionsSummary.add(sum)
-                } else {
-                    //TODO how to handle that
+        if (SHOULD_HANDLE_SERVER_AGREGGATION) {
+            aggregation.chunk?.forEach {
+                if (it.type == EventType.REACTION) {
+                    val eventId = event.eventId ?: ""
+                    val existing = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
+                    if (existing == null) {
+                        val eventSummary = EventAnnotationsSummaryEntity.create(realm, eventId)
+                        eventSummary.roomId = roomId
+                        val sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
+                        sum.key = it.key
+                        sum.firstTimestamp = event.originServerTs ?: 0 //TODO how to maintain order?
+                        sum.count = it.count
+                        eventSummary.reactionsSummary.add(sum)
+                    } else {
+                        //TODO how to handle that
+                    }
                 }
             }
         }

From d2f648edec84a0ff0424738395f7c42c39b08a19 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Tue, 4 Jun 2019 17:16:02 +0200
Subject: [PATCH 03/18] Use Font emoji compat for quickReactions and pills

---
 .../riotredesign/EmojiCompatFontProvider.kt   | 48 +++++++++++++++++
 .../vector/riotredesign/VectorApplication.kt  | 29 +++++++++-
 .../vector/riotredesign/core/di/AppModule.kt  |  5 ++
 .../riotredesign/features/home/HomeModule.kt  |  3 +-
 .../timeline/action/QuickReactionFragment.kt  | 17 ++++--
 .../timeline/factory/MessageItemFactory.kt    | 33 +++++++-----
 .../detail/timeline/item/AbsMessageItem.kt    |  5 ++
 .../reactions/EmojiReactionPickerActivity.kt  | 54 ++++++-------------
 .../reactions/widget/ReactionButton.kt        | 14 +++--
 9 files changed, 149 insertions(+), 59 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/riotredesign/EmojiCompatFontProvider.kt

diff --git a/vector/src/main/java/im/vector/riotredesign/EmojiCompatFontProvider.kt b/vector/src/main/java/im/vector/riotredesign/EmojiCompatFontProvider.kt
new file mode 100644
index 0000000000..a928d48b5f
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotredesign/EmojiCompatFontProvider.kt
@@ -0,0 +1,48 @@
+package im.vector.riotredesign
+
+import android.graphics.Typeface
+import androidx.core.provider.FontsContractCompat
+import timber.log.Timber
+
+
+class EmojiCompatFontProvider : FontsContractCompat.FontRequestCallback() {
+
+    var typeface: Typeface? = null
+        set(value) {
+            if (value != field) {
+                field = value
+                listeners.forEach {
+                    try {
+                        it.compatibilityFontUpdate(value)
+                    } catch (t: Throwable) {
+                        Timber.e(t)
+                    }
+                }
+            }
+        }
+
+    private val listeners = ArrayList<FontProviderListener>()
+
+    override fun onTypefaceRetrieved(typeface: Typeface) {
+        this.typeface = typeface
+    }
+
+    override fun onTypefaceRequestFailed(reason: Int) {
+        Timber.e("Failed to load Emoji Compatible font, reason:$reason")
+    }
+
+    fun addListener(listener: FontProviderListener) {
+        if (!listeners.contains(listener)) {
+            listeners.add(listener)
+        }
+    }
+
+    fun removeListener(listener: FontProviderListener) {
+        listeners.remove(listener)
+    }
+
+
+    interface FontProviderListener {
+        fun compatibilityFontUpdate(typeface: Typeface?)
+    }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt
index 88cee67496..e1d85fe3d6 100644
--- a/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt
+++ b/vector/src/main/java/im/vector/riotredesign/VectorApplication.kt
@@ -18,6 +18,10 @@ package im.vector.riotredesign
 
 import android.app.Application
 import android.content.Context
+import android.os.Handler
+import android.os.HandlerThread
+import androidx.core.provider.FontRequest
+import androidx.core.provider.FontsContractCompat
 import android.content.res.Configuration
 import androidx.multidex.MultiDex
 import com.airbnb.epoxy.EpoxyAsyncUtil
@@ -41,6 +45,10 @@ import timber.log.Timber
 
 class VectorApplication : Application() {
 
+    //font thread handler
+    private var mFontThreadHandler: Handler? = null
+
+
     val vectorConfiguration: VectorConfiguration by inject()
 
     override fun onCreate() {
@@ -63,10 +71,20 @@ class VectorApplication : Application() {
         val appModule = AppModule(applicationContext).definition
         val homeModule = HomeModule().definition
         val roomDirectoryModule = RoomDirectoryModule().definition
-        startKoin(listOf(appModule, homeModule, roomDirectoryModule), logger = EmptyLogger())
+        val koin = startKoin(listOf(appModule, homeModule, roomDirectoryModule), logger = EmptyLogger())
 
         Matrix.getInstance().setApplicationFlavor(BuildConfig.FLAVOR_DESCRIPTION)
 
+        val fontRequest = FontRequest(
+                "com.google.android.gms.fonts",
+                "com.google.android.gms",
+                "Noto Color Emoji Compat",
+                R.array.com_google_android_gms_fonts_certs
+        )
+
+//        val efp = koin.koinContext.get<EmojiCompatFontProvider>()
+        FontsContractCompat.requestFont(this, fontRequest, koin.koinContext.get<EmojiCompatFontProvider>(), getFontThreadHandler())
+
         vectorConfiguration.initConfiguration()
     }
 
@@ -81,4 +99,13 @@ class VectorApplication : Application() {
         vectorConfiguration.onConfigurationChanged(newConfig)
     }
 
+    private fun getFontThreadHandler(): Handler {
+        if (mFontThreadHandler == null) {
+            val handlerThread = HandlerThread("fonts")
+            handlerThread.start()
+            mFontThreadHandler = Handler(handlerThread.looper)
+        }
+        return mFontThreadHandler!!
+    }
+
 }
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt
index dc3068cfa3..542c0953e7 100644
--- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt
+++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt
@@ -20,6 +20,7 @@ import android.content.Context
 import android.content.Context.MODE_PRIVATE
 import androidx.fragment.app.Fragment
 import im.vector.matrix.android.api.Matrix
+import im.vector.riotredesign.EmojiCompatFontProvider
 import im.vector.riotredesign.core.error.ErrorFormatter
 import im.vector.riotredesign.core.resources.LocaleProvider
 import im.vector.riotredesign.core.resources.StringArrayProvider
@@ -90,5 +91,9 @@ class AppModule(private val context: Context) {
             DefaultNavigator(fragment) as Navigator
         }
 
+        single {
+            EmojiCompatFontProvider()
+        }
+
     }
 }
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt
index d6297b6ff6..ce985abbf2 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeModule.kt
@@ -72,7 +72,8 @@ class HomeModule {
             val timelineMediaSizeProvider = TimelineMediaSizeProvider()
             val colorProvider = ColorProvider(fragment.requireContext())
             val timelineDateFormatter = get<TimelineDateFormatter>()
-            val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider, timelineDateFormatter, eventHtmlRenderer, get())
+            val messageItemFactory = MessageItemFactory(colorProvider, timelineMediaSizeProvider,
+                    timelineDateFormatter, eventHtmlRenderer, get(), get())
 
             val timelineItemFactory = TimelineItemFactory(
                     messageItemFactory = messageItemFactory,
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt
index eac10a9044..2d601544c8 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt
@@ -15,6 +15,7 @@
  */
 package im.vector.riotredesign.features.home.room.detail.timeline.action
 
+import android.graphics.Typeface
 import android.os.Bundle
 import android.view.LayoutInflater
 import android.view.View
@@ -28,7 +29,9 @@ import com.airbnb.mvrx.BaseMvRxFragment
 import com.airbnb.mvrx.MvRx
 import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
+import im.vector.riotredesign.EmojiCompatFontProvider
 import im.vector.riotredesign.R
+import org.koin.android.ext.android.inject
 
 /**
  * Quick Reaction Fragment (agree / like reactions)
@@ -54,6 +57,8 @@ class QuickReactionFragment : BaseMvRxFragment() {
 
     var interactionListener: InteractionListener? = null
 
+    val fontProvider by inject<EmojiCompatFontProvider>()
+
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
         val view = inflater.inflate(R.layout.adapter_item_action_quick_reaction, container, false)
         ButterKnife.bind(this, view)
@@ -68,6 +73,10 @@ class QuickReactionFragment : BaseMvRxFragment() {
         quickReact3Text.text = QuickReactionViewModel.likePositive
         quickReact4Text.text = QuickReactionViewModel.likeNegative
 
+        listOf(quickReact1Text, quickReact2Text, quickReact3Text, quickReact4Text).forEach {
+            it.typeface = fontProvider.typeface ?: Typeface.DEFAULT
+        }
+
         //configure click listeners
         quickReact1Text.setOnClickListener {
             viewModel.toggleAgree(true)
@@ -88,11 +97,11 @@ class QuickReactionFragment : BaseMvRxFragment() {
 
         TransitionManager.beginDelayedTransition(rootLayout)
         when (it.agreeTrigleState) {
-            TriggleState.NONE -> {
+            TriggleState.NONE   -> {
                 quickReact1Text.alpha = 1f
                 quickReact2Text.alpha = 1f
             }
-            TriggleState.FIRST -> {
+            TriggleState.FIRST  -> {
                 quickReact1Text.alpha = 1f
                 quickReact2Text.alpha = 0.2f
 
@@ -103,11 +112,11 @@ class QuickReactionFragment : BaseMvRxFragment() {
             }
         }
         when (it.likeTriggleState) {
-            TriggleState.NONE -> {
+            TriggleState.NONE   -> {
                 quickReact3Text.alpha = 1f
                 quickReact4Text.alpha = 1f
             }
-            TriggleState.FIRST -> {
+            TriggleState.FIRST  -> {
                 quickReact3Text.alpha = 1f
                 quickReact4Text.alpha = 0.2f
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index d61848af6d..7571fead47 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -33,6 +33,7 @@ import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
 import im.vector.matrix.android.api.session.room.model.message.*
 import im.vector.matrix.android.api.session.room.send.SendState
 import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
+import im.vector.riotredesign.EmojiCompatFontProvider
 import im.vector.riotredesign.R
 import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
 import im.vector.riotredesign.core.extensions.localDateTime
@@ -55,7 +56,8 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                          private val timelineMediaSizeProvider: TimelineMediaSizeProvider,
                          private val timelineDateFormatter: TimelineDateFormatter,
                          private val htmlRenderer: EventHtmlRenderer,
-                         private val stringProvider: StringProvider) {
+                         private val stringProvider: StringProvider,
+                         private val emojiCompatFontProvider: EmojiCompatFontProvider) {
 
     fun create(event: TimelineEvent,
                nextEvent: TimelineEvent?,
@@ -115,24 +117,24 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
 //        val all = event.root.toContent()
 //        val ev = all.toModel<Event>()
         return when (messageContent) {
-            is MessageEmoteContent -> buildEmoteMessageItem(messageContent,
+            is MessageEmoteContent  -> buildEmoteMessageItem(messageContent,
                     informationData,
                     hasBeenEdited,
                     event.annotations?.editSummary,
                     callback)
-            is MessageTextContent -> buildTextMessageItem(event.sendState,
+            is MessageTextContent   -> buildTextMessageItem(event.sendState,
                     messageContent,
                     informationData,
                     hasBeenEdited,
                     event.annotations?.editSummary,
                     callback
             )
-            is MessageImageContent -> buildImageMessageItem(messageContent, informationData, callback)
+            is MessageImageContent  -> buildImageMessageItem(messageContent, informationData, callback)
             is MessageNoticeContent -> buildNoticeMessageItem(messageContent, informationData, callback)
-            is MessageVideoContent -> buildVideoMessageItem(messageContent, informationData, callback)
-            is MessageFileContent -> buildFileMessageItem(messageContent, informationData, callback)
-            is MessageAudioContent -> buildAudioMessageItem(messageContent, informationData, callback)
-            else -> buildNotHandledMessageItem(messageContent)
+            is MessageVideoContent  -> buildVideoMessageItem(messageContent, informationData, callback)
+            is MessageFileContent   -> buildFileMessageItem(messageContent, informationData, callback)
+            is MessageAudioContent  -> buildAudioMessageItem(messageContent, informationData, callback)
+            else                    -> buildNotHandledMessageItem(messageContent)
         }
     }
 
@@ -144,20 +146,21 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                 .filename(messageContent.body)
                 .iconRes(R.drawable.filetype_audio)
                 .reactionPillCallback(callback)
+                .emojiTypeFace(emojiCompatFontProvider.typeface)
                 .avatarClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
+                        DebouncedClickListener(View.OnClickListener {
                             callback?.onAvatarClicked(informationData)
                         }))
                 .memberClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
+                        DebouncedClickListener(View.OnClickListener {
                             callback?.onMemberNameClicked(informationData)
                         }))
                 .cellClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
+                        DebouncedClickListener(View.OnClickListener { view: View ->
                             callback?.onEventCellClicked(informationData, messageContent, view)
                         }))
                 .clickListener(
-                        DebouncedClickListener(View.OnClickListener { _ ->
+                        DebouncedClickListener(View.OnClickListener {
                             callback?.onAudioMessageClicked(messageContent)
                         }))
                 .longClickListener { view ->
@@ -173,6 +176,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                 .informationData(informationData)
                 .filename(messageContent.body)
                 .reactionPillCallback(callback)
+                .emojiTypeFace(emojiCompatFontProvider.typeface)
                 .iconRes(R.drawable.filetype_attachment)
                 .avatarClickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
@@ -221,6 +225,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                 .informationData(informationData)
                 .mediaData(data)
                 .reactionPillCallback(callback)
+                .emojiTypeFace(emojiCompatFontProvider.typeface)
                 .avatarClickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
                             callback?.onAvatarClicked(informationData)
@@ -268,6 +273,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                 .informationData(informationData)
                 .mediaData(thumbnailData)
                 .reactionPillCallback(callback)
+                .emojiTypeFace(emojiCompatFontProvider.typeface)
                 .avatarClickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
                             callback?.onAvatarClicked(informationData)
@@ -311,6 +317,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                 }
                 .informationData(informationData)
                 .reactionPillCallback(callback)
+                .emojiTypeFace(emojiCompatFontProvider.typeface)
                 .avatarClickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
                             callback?.onAvatarClicked(informationData)
@@ -384,6 +391,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                 .message(message)
                 .informationData(informationData)
                 .reactionPillCallback(callback)
+                .emojiTypeFace(emojiCompatFontProvider.typeface)
                 .avatarClickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
                             callback?.onAvatarClicked(informationData)
@@ -423,6 +431,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                 }
                 .informationData(informationData)
                 .reactionPillCallback(callback)
+                .emojiTypeFace(emojiCompatFontProvider.typeface)
                 .avatarClickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
                             callback?.onAvatarClicked(informationData)
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt
index 0ce01a88fc..b74f7bcac5 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt
@@ -16,6 +16,7 @@
 
 package im.vector.riotredesign.features.home.room.detail.timeline.item
 
+import android.graphics.Typeface
 import android.os.Build
 import android.view.View
 import android.view.ViewGroup
@@ -50,6 +51,9 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
     @EpoxyAttribute
     var memberClickListener: View.OnClickListener? = null
 
+    @EpoxyAttribute
+    var emojiTypeFace: Typeface? = null
+
     @EpoxyAttribute
     var reactionPillCallback: TimelineEventController.ReactionPillCallback? = null
 
@@ -116,6 +120,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
                     idToRefInFlow.add(reactionButton.id)
                     reactionButton.reactionString = reaction.key
                     reactionButton.reactionCount = reaction.count
+                    reactionButton.emojiTypeFace = emojiTypeFace
                     reactionButton.setChecked(reaction.addedByMe)
                     reactionButton.isEnabled = reaction.synced
                 }
diff --git a/vector/src/main/java/im/vector/riotredesign/features/reactions/EmojiReactionPickerActivity.kt b/vector/src/main/java/im/vector/riotredesign/features/reactions/EmojiReactionPickerActivity.kt
index a75accd9ba..7ad57d5368 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/reactions/EmojiReactionPickerActivity.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/reactions/EmojiReactionPickerActivity.kt
@@ -19,23 +19,20 @@ import android.app.Activity
 import android.content.Context
 import android.content.Intent
 import android.graphics.Typeface
-import android.os.Handler
-import android.os.HandlerThread
 import android.util.TypedValue
 import android.view.Menu
 import android.view.MenuInflater
 import android.view.MenuItem
 import android.widget.SearchView
 import androidx.appcompat.widget.Toolbar
-import androidx.core.provider.FontRequest
-import androidx.core.provider.FontsContractCompat
 import androidx.lifecycle.Observer
 import androidx.lifecycle.ViewModelProviders
 import com.google.android.material.tabs.TabLayout
+import im.vector.riotredesign.EmojiCompatFontProvider
 import im.vector.riotredesign.R
 import im.vector.riotredesign.core.platform.VectorBaseActivity
 import kotlinx.android.synthetic.main.activity_emoji_reaction_picker.*
-import timber.log.Timber
+import org.koin.android.ext.android.inject
 
 /**
  *
@@ -44,20 +41,21 @@ import timber.log.Timber
  * TODO: Finish Refactor to vector base activity
  * TODO: Move font request to app
  */
-class EmojiReactionPickerActivity : VectorBaseActivity() {
+class EmojiReactionPickerActivity : VectorBaseActivity(), EmojiCompatFontProvider.FontProviderListener {
+
 
     private lateinit var tabLayout: TabLayout
 
     lateinit var viewModel: EmojiChooserViewModel
 
-    private var mHandler: Handler? = null
-
     override fun getMenuRes(): Int = R.menu.menu_emoji_reaction_picker
 
     override fun getLayoutRes(): Int = R.layout.activity_emoji_reaction_picker
 
     override fun getTitleRes(): Int = R.string.title_activity_emoji_reaction_picker
 
+    val emojiCompatFontProvider by inject<EmojiCompatFontProvider>()
+
     private var tabLayoutSelectionListener = object : TabLayout.BaseOnTabSelectedListener<TabLayout.Tab> {
         override fun onTabReselected(p0: TabLayout.Tab) {
         }
@@ -71,19 +69,13 @@ class EmojiReactionPickerActivity : VectorBaseActivity() {
 
     }
 
-    private fun getFontThreadHandler(): Handler {
-        if (mHandler == null) {
-            val handlerThread = HandlerThread("fonts")
-            handlerThread.start()
-            mHandler = Handler(handlerThread.looper)
-        }
-        return mHandler!!
-    }
-
     override fun initUiAndData() {
         configureToolbar(emojiPickerToolbar)
 
-        requestEmojivUnicode10CompatibleFont()
+        emojiCompatFontProvider.let {
+            EmojiDrawView.configureTextPaint(this, it.typeface)
+            it.addListener(this)
+        }
 
         tabLayout = findViewById(R.id.tabs)
 
@@ -124,27 +116,13 @@ class EmojiReactionPickerActivity : VectorBaseActivity() {
         })
     }
 
-    private fun requestEmojivUnicode10CompatibleFont() {
-        val fontRequest = FontRequest(
-                "com.google.android.gms.fonts",
-                "com.google.android.gms",
-                "Noto Color Emoji Compat",
-                R.array.com_google_android_gms_fonts_certs
-        )
+    override fun compatibilityFontUpdate(typeface: Typeface?) {
+        EmojiDrawView.configureTextPaint(this, typeface)
+    }
 
-        EmojiDrawView.configureTextPaint(this, null)
-        val callback = object : FontsContractCompat.FontRequestCallback() {
-
-            override fun onTypefaceRetrieved(typeface: Typeface) {
-                EmojiDrawView.configureTextPaint(this@EmojiReactionPickerActivity, typeface)
-            }
-
-            override fun onTypefaceRequestFailed(reason: Int) {
-                Timber.e("Failed to load Emoji Compatible font, reason:$reason")
-            }
-        }
-
-        FontsContractCompat.requestFont(this, fontRequest, callback, getFontThreadHandler())
+    override fun onDestroy() {
+        emojiCompatFontProvider.removeListener(this)
+        super.onDestroy()
     }
 
     override fun onCreateOptionsMenu(menu: Menu): Boolean {
diff --git a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt
index c4a87d0fe7..b0bf08e383 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt
@@ -21,6 +21,7 @@ import android.animation.AnimatorSet
 import android.animation.ObjectAnimator
 import android.content.Context
 import android.content.res.TypedArray
+import android.graphics.Typeface
 import android.graphics.drawable.Drawable
 import android.util.AttributeSet
 import android.view.LayoutInflater
@@ -56,6 +57,11 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
 
     private var reactionSelector: View? = null
 
+    var emojiTypeFace: Typeface? = null
+        set(value) {
+            field = value
+            emojiView?.typeface = value ?: Typeface.DEFAULT
+        }
 
     private var dotsView: DotsView
     private var circleView: CircleView
@@ -97,6 +103,8 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
 
         countTextView?.text = reactionCount.toString()
 
+        emojiView?.typeface = this.emojiTypeFace ?: Typeface.DEFAULT
+
         val array = context.obtainStyledAttributes(attrs, R.styleable.ReactionButton, defStyleAttr, 0)
 
         onDrawable = ContextCompat.getDrawable(context, R.drawable.rounded_rect_shape)
@@ -239,7 +247,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
             return true
 
         when (event.action) {
-            MotionEvent.ACTION_DOWN ->
+            MotionEvent.ACTION_DOWN   ->
                 /*
                 Commented out this line and moved the animation effect to the action up event due to
                 conflicts that were occurring when library is used in sliding type views.
@@ -248,7 +256,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
                 */
                 isPressed = true
 
-            MotionEvent.ACTION_MOVE -> {
+            MotionEvent.ACTION_MOVE   -> {
                 val x = event.x
                 val y = event.y
                 val isInside = x > 0 && x < width && y > 0 && y < height
@@ -257,7 +265,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
                 }
             }
 
-            MotionEvent.ACTION_UP -> {
+            MotionEvent.ACTION_UP     -> {
                 emojiView!!.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).interpolator = DECCELERATE_INTERPOLATOR
                 emojiView!!.animate().scaleX(1f).scaleY(1f).interpolator = DECCELERATE_INTERPOLATOR
                 if (isPressed) {

From 440442bb99b45cfb248343f82bb221707ac15d4c Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Wed, 5 Jun 2019 19:23:57 +0200
Subject: [PATCH 04/18] New View Reactions bottom sheet

+ visible on reaction long click
+ Reaction pills size adapt to count, and number format
---
 .../room/model/relation/RelationService.kt    |   3 +
 .../room/relation/DefaultRelationService.kt   |  16 +++
 vector/sampledata/reactions.json              |  22 ++++
 .../riotredesign/core/utils/TextUtils.kt      |  29 +++++
 .../home/room/detail/RoomDetailFragment.kt    |  12 +-
 .../timeline/TimelineEventController.kt       |   1 +
 .../action/MessageActionsBottomSheet.kt       |  15 +--
 .../action/MessageActionsViewModel.kt         |   3 +-
 .../timeline/action/MessageMenuFragment.kt    |   2 +-
 .../timeline/action/MessageMenuViewModel.kt   |   2 +-
 .../timeline/action/QuickReactionFragment.kt  |   2 +-
 .../timeline/action/QuickReactionViewModel.kt |   2 +-
 .../timeline/action/ReactionInfoSimpleItem.kt |  39 +++++++
 .../action/TimelineEventFragmentArgs.kt       |  12 ++
 .../action/ViewReactionBottomSheet.kt         |  71 ++++++++++++
 .../timeline/action/ViewReactionViewModel.kt  | 106 ++++++++++++++++++
 .../action/ViewReactionsEpoxyController.kt    |  19 ++++
 .../detail/timeline/item/AbsMessageItem.kt    |   6 +-
 .../reactions/widget/ReactionButton.kt        |  80 +++++++------
 .../layout/bottom_sheet_display_reactions.xml |  40 +++++++
 .../res/layout/item_simple_reaction_info.xml  |  45 ++++++++
 .../src/main/res/layout/reaction_button.xml   |  32 ++++--
 vector/src/main/res/values/strings_riotX.xml  |   1 +
 23 files changed, 492 insertions(+), 68 deletions(-)
 create mode 100644 vector/sampledata/reactions.json
 create mode 100644 vector/src/main/java/im/vector/riotredesign/core/utils/TextUtils.kt
 create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt
 create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt
 create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt
 create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt
 create mode 100644 vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt
 create mode 100644 vector/src/main/res/layout/bottom_sheet_display_reactions.xml
 create mode 100644 vector/src/main/res/layout/item_simple_reaction_info.xml

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt
index bc92472892..ac08b64f53 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/relation/RelationService.kt
@@ -15,7 +15,9 @@
  */
 package im.vector.matrix.android.api.session.room.model.relation
 
+import androidx.lifecycle.LiveData
 import im.vector.matrix.android.api.session.events.model.Event
+import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
 import im.vector.matrix.android.api.util.Cancelable
 
 /**
@@ -91,4 +93,5 @@ interface RelationService {
      */
     fun replyToMessage(eventReplied: Event, replyText: String): Cancelable?
 
+    fun getEventSummaryLive(eventId: String): LiveData<List<EventAnnotationsSummary>>
 }
\ No newline at end of file
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt
index 6d6e4763d2..264909e956 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/relation/DefaultRelationService.kt
@@ -15,15 +15,19 @@
  */
 package im.vector.matrix.android.internal.session.room.relation
 
+import androidx.lifecycle.LiveData
 import androidx.work.OneTimeWorkRequest
 import com.zhuinden.monarchy.Monarchy
 import im.vector.matrix.android.api.MatrixCallback
 import im.vector.matrix.android.api.session.events.model.Event
+import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
 import im.vector.matrix.android.api.session.room.model.message.MessageType
 import im.vector.matrix.android.api.session.room.model.relation.RelationService
 import im.vector.matrix.android.api.util.Cancelable
 import im.vector.matrix.android.internal.database.helper.addSendingEvent
+import im.vector.matrix.android.internal.database.mapper.asDomain
 import im.vector.matrix.android.internal.database.model.ChunkEntity
+import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
 import im.vector.matrix.android.internal.database.model.RoomEntity
 import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
 import im.vector.matrix.android.internal.database.query.where
@@ -169,6 +173,18 @@ internal class DefaultRelationService(private val roomId: String,
         return CancelableWork(workRequest.id)
     }
 
+
+    override fun getEventSummaryLive(eventId: String): LiveData<List<EventAnnotationsSummary>> {
+        return monarchy.findAllMappedWithChanges(
+                { realm ->
+                    EventAnnotationsSummaryEntity.where(realm, eventId)
+                },
+                {
+                    it.asDomain()
+                }
+        )
+    }
+
     /**
      * Saves the event in database as a local echo.
      * SendState is set to UNSENT and it's added to a the sendingTimelineEvents list of the room.
diff --git a/vector/sampledata/reactions.json b/vector/sampledata/reactions.json
new file mode 100644
index 0000000000..e2c8e4f4cd
--- /dev/null
+++ b/vector/sampledata/reactions.json
@@ -0,0 +1,22 @@
+{
+  "data": [
+    {
+      "reaction" : "👍"
+    },
+    {
+      "reaction" : "😀"
+    },
+    {
+      "reaction" : "😞"
+    },
+    {
+      "reaction" : "Not a reaction"
+    },
+    {
+      "reaction" : "✅"
+    },
+    {
+      "reaction" : "🎉"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/core/utils/TextUtils.kt b/vector/src/main/java/im/vector/riotredesign/core/utils/TextUtils.kt
new file mode 100644
index 0000000000..558275565a
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotredesign/core/utils/TextUtils.kt
@@ -0,0 +1,29 @@
+package im.vector.riotredesign.core.utils
+
+import java.util.*
+
+object TextUtils {
+
+    private val suffixes = TreeMap<Int, String>().also {
+        it.put(1000, "k")
+        it.put(1000000, "M")
+        it.put(1000000000, "G")
+    }
+
+    fun formatCountToShortDecimal(value: Int): String {
+        try {
+            if (value < 0) return "-" + formatCountToShortDecimal(-value)
+            if (value < 1000) return value.toString() //deal with easy case
+
+            val e = suffixes.floorEntry(value)
+            val divideBy = e.key
+            val suffix = e.value
+
+            val truncated = value / (divideBy!! / 10) //the number part of the output times 10
+            val hasDecimal = truncated < 100 && truncated / 10.0 != (truncated / 10).toDouble()
+            return if (hasDecimal) "${truncated / 10.0}$suffix" else "${truncated / 10}$suffix"
+        } catch (t: Throwable) {
+            return value.toString()
+        }
+    }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
index 4653d75166..5ab0e46c94 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
@@ -84,6 +84,7 @@ import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventCo
 import im.vector.riotredesign.features.home.room.detail.timeline.action.ActionsHandler
 import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageActionsBottomSheet
 import im.vector.riotredesign.features.home.room.detail.timeline.action.MessageMenuViewModel
+import im.vector.riotredesign.features.home.room.detail.timeline.action.ViewReactionBottomSheet
 import im.vector.riotredesign.features.home.room.detail.timeline.helper.EndlessRecyclerViewScrollListener
 import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
 import im.vector.riotredesign.features.html.PillImageSpan
@@ -235,11 +236,13 @@ class RoomDetailFragment :
                     var formattedBody: CharSequence? = null
                     if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
                         val parser = Parser.builder().build()
-                        val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
+                        val document = parser.parse(messageContent.formattedBody
+                                ?: messageContent.body)
                         formattedBody = Markwon.builder(requireContext())
                                 .usePlugin(HtmlPlugin.create()).build().render(document)
                     }
-                    composerLayout.composerRelatedMessageContent.text = formattedBody ?: nonFormattedBody
+                    composerLayout.composerRelatedMessageContent.text = formattedBody
+                            ?: nonFormattedBody
 
 
                     if (mode == SendMode.EDIT) {
@@ -593,6 +596,11 @@ class RoomDetailFragment :
         }
     }
 
+    override fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String) {
+        ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, informationData)
+                .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
+    }
+
     override fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?) {
         editAggregatedSummary?.also {
             roomDetailViewModel.process(RoomDetailActions.ShowEditHistoryAction(informationData.eventId, it))
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
index 84f970471e..175702cfa8 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
@@ -62,6 +62,7 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
 
     interface ReactionPillCallback {
         fun onClickOnReactionPill(informationData: MessageInformationData, reaction: String, on: Boolean)
+        fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String)
     }
 
     private val collapsedEventIds = linkedSetOf<String>()
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
index 44a753afbf..fa94043ed4 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
@@ -17,7 +17,6 @@ package im.vector.riotredesign.features.home.room.detail.timeline.action
 
 import android.app.Dialog
 import android.os.Bundle
-import android.os.Parcelable
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -36,7 +35,6 @@ import im.vector.riotredesign.R
 import im.vector.riotredesign.core.glide.GlideApp
 import im.vector.riotredesign.features.home.AvatarRenderer
 import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
-import kotlinx.android.parcel.Parcelize
 
 /**
  * Bottom sheet fragment that shows a message preview with list of contextual actions
@@ -74,7 +72,7 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {
         val cfm = childFragmentManager
         var menuActionFragment = cfm.findFragmentByTag("MenuActionFragment") as? MessageMenuFragment
         if (menuActionFragment == null) {
-            menuActionFragment = MessageMenuFragment.newInstance(arguments!!.get(MvRx.KEY_ARG) as ParcelableArgs)
+            menuActionFragment = MessageMenuFragment.newInstance(arguments!!.get(MvRx.KEY_ARG) as TimelineEventFragmentArgs)
             cfm.beginTransaction()
                     .replace(R.id.bottom_sheet_menu_container, menuActionFragment, "MenuActionFragment")
                     .commit()
@@ -89,7 +87,7 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {
 
         var quickReactionFragment = cfm.findFragmentByTag("QuickReaction") as? QuickReactionFragment
         if (quickReactionFragment == null) {
-            quickReactionFragment = QuickReactionFragment.newInstance(arguments!!.get(MvRx.KEY_ARG) as ParcelableArgs)
+            quickReactionFragment = QuickReactionFragment.newInstance(arguments!!.get(MvRx.KEY_ARG) as TimelineEventFragmentArgs)
             cfm.beginTransaction()
                     .replace(R.id.bottom_sheet_quick_reaction_container, quickReactionFragment, "QuickReaction")
                     .commit()
@@ -135,18 +133,11 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {
     }
 
 
-    @Parcelize
-    data class ParcelableArgs(
-            val eventId: String,
-            val roomId: String,
-            val informationData: MessageInformationData
-    ) : Parcelable
-
     companion object {
         fun newInstance(roomId: String, informationData: MessageInformationData): MessageActionsBottomSheet {
             return MessageActionsBottomSheet().apply {
                 setArguments(
-                        ParcelableArgs(
+                        TimelineEventFragmentArgs(
                                 informationData.eventId,
                                 roomId,
                                 informationData
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index 9198f8ee83..a307afe35a 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -25,7 +25,6 @@ import im.vector.matrix.android.api.session.room.model.message.MessageTextConten
 import im.vector.matrix.android.api.session.room.model.message.MessageType
 import im.vector.riotredesign.core.platform.VectorViewModel
 import org.commonmark.parser.Parser
-import org.commonmark.renderer.html.HtmlRenderer
 import org.koin.android.ext.android.get
 import ru.noties.markwon.Markwon
 import ru.noties.markwon.html.HtmlPlugin
@@ -51,7 +50,7 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode
 
         override fun initialState(viewModelContext: ViewModelContext): MessageActionState? {
             val currentSession = viewModelContext.activity.get<Session>()
-            val parcel = viewModelContext.args as MessageActionsBottomSheet.ParcelableArgs
+            val parcel = viewModelContext.args as TimelineEventFragmentArgs
 
             val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuFragment.kt
index 3009f5fc67..2b47eae327 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuFragment.kt
@@ -101,7 +101,7 @@ class MessageMenuFragment : BaseMvRxFragment() {
 
 
     companion object {
-        fun newInstance(pa: MessageActionsBottomSheet.ParcelableArgs): MessageMenuFragment {
+        fun newInstance(pa: TimelineEventFragmentArgs): MessageMenuFragment {
             val args = Bundle()
             args.putParcelable(MvRx.KEY_ARG, pa)
             val fragment = MessageMenuFragment()
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
index 86ddf8665d..776be95042 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
@@ -46,7 +46,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
         override fun initialState(viewModelContext: ViewModelContext): MessageMenuState? {
             // Args are accessible from the context.
             val currentSession = viewModelContext.activity.get<Session>()
-            val parcel = viewModelContext.args as MessageActionsBottomSheet.ParcelableArgs
+            val parcel = viewModelContext.args as TimelineEventFragmentArgs
             val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
                     ?: return null
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt
index 2d601544c8..b10744809a 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionFragment.kt
@@ -139,7 +139,7 @@ class QuickReactionFragment : BaseMvRxFragment() {
     }
 
     companion object {
-        fun newInstance(pa: MessageActionsBottomSheet.ParcelableArgs): QuickReactionFragment {
+        fun newInstance(pa: TimelineEventFragmentArgs): QuickReactionFragment {
             val args = Bundle()
             args.putParcelable(MvRx.KEY_ARG, pa)
             val fragment = QuickReactionFragment()
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt
index 36a07bee59..89976248b8 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt
@@ -124,7 +124,7 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel
             // Args are accessible from the context.
             // val foo = vieWModelContext.args<MyArgs>.foo
             val currentSession = viewModelContext.activity.get<Session>()
-            val parcel = viewModelContext.args as MessageActionsBottomSheet.ParcelableArgs
+            val parcel = viewModelContext.args as TimelineEventFragmentArgs
             val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
                     ?: return null
             var agreeTriggle: TriggleState = TriggleState.NONE
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt
new file mode 100644
index 0000000000..7c7e253167
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt
@@ -0,0 +1,39 @@
+package im.vector.riotredesign.features.home.room.detail.timeline.action
+
+import android.widget.TextView
+import androidx.core.view.isVisible
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.airbnb.epoxy.EpoxyModelWithHolder
+import im.vector.riotredesign.R
+import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
+
+
+@EpoxyModelClass(layout = R.layout.item_simple_reaction_info)
+abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleItem.Holder>() {
+
+    @EpoxyAttribute
+    lateinit var reactionKey: CharSequence
+    @EpoxyAttribute
+    lateinit var authorDisplayName: CharSequence
+    @EpoxyAttribute
+    var timeStamp: CharSequence? = null
+
+    override fun bind(holder: Holder) {
+        holder.titleView.text = reactionKey
+        holder.displayNameView.text = authorDisplayName
+        timeStamp?.let {
+            holder.timeStampView.text = it
+            holder.timeStampView.isVisible = true
+        } ?: run {
+            holder.timeStampView.isVisible = false
+        }
+    }
+
+    class Holder : VectorEpoxyHolder() {
+        val titleView by bind<TextView>(R.id.itemSimpleReactionInfoKey)
+        val displayNameView by bind<TextView>(R.id.itemSimpleReactionInfoMemberName)
+        val timeStampView by bind<TextView>(R.id.itemSimpleReactionInfoTime)
+    }
+
+}
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt
new file mode 100644
index 0000000000..5764563812
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/TimelineEventFragmentArgs.kt
@@ -0,0 +1,12 @@
+package im.vector.riotredesign.features.home.room.detail.timeline.action
+
+import android.os.Parcelable
+import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
+import kotlinx.android.parcel.Parcelize
+
+@Parcelize
+data class TimelineEventFragmentArgs(
+        val eventId: String,
+        val roomId: String,
+        val informationData: MessageInformationData
+) : Parcelable
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt
new file mode 100644
index 0000000000..a53dc3bd78
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt
@@ -0,0 +1,71 @@
+package im.vector.riotredesign.features.home.room.detail.timeline.action
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.core.view.isVisible
+import androidx.recyclerview.widget.DividerItemDecoration
+import butterknife.BindView
+import butterknife.ButterKnife
+import com.airbnb.epoxy.EpoxyRecyclerView
+import com.airbnb.mvrx.MvRx
+import com.airbnb.mvrx.args
+import com.airbnb.mvrx.fragmentViewModel
+import com.airbnb.mvrx.withState
+import im.vector.riotredesign.R
+import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
+import kotlinx.android.synthetic.main.bottom_sheet_display_reactions.*
+
+
+class ViewReactionBottomSheet : BaseMvRxBottomSheetDialog() {
+
+    private val viewModel: ViewReactionViewModel by fragmentViewModel(ViewReactionViewModel::class)
+
+    private val eventArgs: TimelineEventFragmentArgs by args()
+
+    @BindView(R.id.bottom_sheet_display_reactions_list)
+    lateinit var epoxyRecyclerView: EpoxyRecyclerView
+
+    private val epoxyController by lazy { ViewReactionsEpoxyController() }
+
+    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
+        val view = inflater.inflate(R.layout.bottom_sheet_display_reactions, container, false)
+        ButterKnife.bind(this, view)
+        return view
+    }
+
+    override fun onActivityCreated(savedInstanceState: Bundle?) {
+        super.onActivityCreated(savedInstanceState)
+        epoxyRecyclerView.setController(epoxyController)
+        val dividerItemDecoration = DividerItemDecoration(epoxyRecyclerView.context,
+                LinearLayout.VERTICAL)
+        epoxyRecyclerView.addItemDecoration(dividerItemDecoration)
+    }
+
+
+    override fun invalidate() = withState(viewModel) {
+        if (it.mapReactionKeyToMemberList() == null) {
+            bottomSheetViewReactionSpinner.isVisible = true
+            bottomSheetViewReactionSpinner.animate()
+        } else {
+            bottomSheetViewReactionSpinner.isVisible = false
+        }
+        epoxyController.setData(it)
+    }
+
+    companion object {
+        fun newInstance(roomId: String, informationData: MessageInformationData): ViewReactionBottomSheet {
+            val args = Bundle()
+            val parcelableArgs = TimelineEventFragmentArgs(
+                    informationData.eventId,
+                    roomId,
+                    informationData
+            )
+            args.putParcelable(MvRx.KEY_ARG, parcelableArgs)
+            return ViewReactionBottomSheet().apply { arguments = args }
+
+        }
+    }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt
new file mode 100644
index 0000000000..a5e7fdd882
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt
@@ -0,0 +1,106 @@
+package im.vector.riotredesign.features.home.room.detail.timeline.action
+
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.Observer
+import com.airbnb.mvrx.*
+import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
+import im.vector.riotredesign.core.extensions.localDateTime
+import im.vector.riotredesign.core.platform.VectorViewModel
+import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import org.koin.android.ext.android.get
+
+
+data class DisplayReactionsViewState(
+        val eventId: String = "",
+        val roomId: String = "",
+        val mapReactionKeyToMemberList: Async<List<ReactionInfo>> = Uninitialized)
+    : MvRxState
+
+data class ReactionInfo(
+        val eventId: String,
+        val reactionKey: String,
+        val authorId: String,
+        val authorName: String? = null,
+        val timestamp: String? = null
+)
+
+/**
+ * Used to display the list of members that reacted to a given event
+ */
+class ViewReactionViewModel(private val session: Session,
+                            private val timelineDateFormatter: TimelineDateFormatter,
+                            lifecycleOwner: LifecycleOwner?,
+                            liveSummary: LiveData<List<EventAnnotationsSummary>>?,
+                            initialState: DisplayReactionsViewState) : VectorViewModel<DisplayReactionsViewState>(initialState) {
+
+    init {
+        loadReaction()
+        if (lifecycleOwner != null) {
+            liveSummary?.observe(lifecycleOwner, Observer {
+                it?.firstOrNull()?.let {
+                    loadReaction()
+                }
+            })
+        }
+
+    }
+
+    private fun loadReaction() = withState { state ->
+
+        GlobalScope.launch {
+            try {
+                val room = session.getRoom(state.roomId)
+                val event = room?.getTimeLineEvent(state.eventId)
+                if (event == null) {
+                    setState { copy(mapReactionKeyToMemberList = Fail(Throwable())) }
+                    return@launch
+                }
+                var results = ArrayList<ReactionInfo>()
+                event.annotations?.reactionsSummary?.forEach { sum ->
+
+                    sum.sourceEvents.mapNotNull { room.getTimeLineEvent(it) }.forEach {
+                        val localDate = it.root.localDateTime()
+                        results.add(ReactionInfo(it.root.eventId!!, sum.key, it.root.sender
+                                ?: "", it.senderName, timelineDateFormatter.formatMessageHour(localDate)))
+                    }
+                }
+                setState {
+                    copy(
+                            mapReactionKeyToMemberList = Success(results.sortedBy { it.timestamp })
+                    )
+                }
+            } catch (t: Throwable) {
+                setState {
+                    copy(
+                            mapReactionKeyToMemberList = Fail(t)
+                    )
+                }
+            }
+        }
+    }
+
+
+    companion object : MvRxViewModelFactory<ViewReactionViewModel, DisplayReactionsViewState> {
+
+        override fun initialState(viewModelContext: ViewModelContext): DisplayReactionsViewState? {
+
+            val roomId = (viewModelContext.args as? TimelineEventFragmentArgs)?.roomId
+                    ?: return null
+            val info = (viewModelContext.args as? TimelineEventFragmentArgs)?.informationData
+                    ?: return null
+            return DisplayReactionsViewState(info.eventId, roomId)
+        }
+
+        override fun create(viewModelContext: ViewModelContext, state: DisplayReactionsViewState): ViewReactionViewModel? {
+            val session = viewModelContext.activity.get<Session>()
+            val eventId = (viewModelContext.args as TimelineEventFragmentArgs).eventId
+            return ViewReactionViewModel(session, viewModelContext.activity.get(), viewModelContext.activity, session.getRoom(state.roomId)?.getEventSummaryLive(eventId), state)
+        }
+
+
+    }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt
new file mode 100644
index 0000000000..a3d146a24f
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt
@@ -0,0 +1,19 @@
+package im.vector.riotredesign.features.home.room.detail.timeline.action
+
+import com.airbnb.epoxy.TypedEpoxyController
+
+
+class ViewReactionsEpoxyController : TypedEpoxyController<DisplayReactionsViewState>() {
+
+    override fun buildModels(state: DisplayReactionsViewState) {
+        val map = state.mapReactionKeyToMemberList() ?: return
+        map.forEach {
+            reactionInfoSimpleItem {
+                id(it.eventId)
+                timeStamp(it.timestamp)
+                reactionKey(it.reactionKey)
+                authorDisplayName(it.authorName ?: it.authorId)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt
index b74f7bcac5..a3f43baf51 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt
@@ -65,6 +65,10 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
         override fun onUnReacted(reactionButton: ReactionButton) {
             reactionPillCallback?.onClickOnReactionPill(informationData, reactionButton.reactionString, false)
         }
+
+        override fun onLongClick(reactionButton: ReactionButton) {
+            reactionPillCallback?.onLongClickOnReactionPill(informationData, reactionButton.reactionString)
+        }
     }
 
     override fun bind(holder: H) {
@@ -112,7 +116,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
             //clear all reaction buttons (but not the Flow helper!)
             holder.reactionWrapper?.children?.forEach { (it as? ReactionButton)?.isGone = true }
             val idToRefInFlow = ArrayList<Int>()
-            informationData.orderedReactionList?.chunked(7)?.firstOrNull()?.forEachIndexed { index, reaction ->
+            informationData.orderedReactionList?.chunked(8)?.firstOrNull()?.forEachIndexed { index, reaction ->
                 (holder.reactionWrapper?.children?.elementAtOrNull(index) as? ReactionButton)?.let { reactionButton ->
                     reactionButton.isVisible = true
                     reactionButton.reactedListener = reactionClickListener
diff --git a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt
index b0bf08e383..c0716cf28e 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt
@@ -37,13 +37,14 @@ import androidx.annotation.ColorInt
 import androidx.annotation.ColorRes
 import androidx.core.content.ContextCompat
 import im.vector.riotredesign.R
+import im.vector.riotredesign.core.utils.TextUtils
 
 /**
  * An animated reaction button.
  * Displays a String reaction (emoji), with a count, and that can be selected or not (toggle)
  */
 class ReactionButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
-                                               defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr), View.OnClickListener {
+                                               defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener {
 
     companion object {
         private val DECCELERATE_INTERPOLATOR = DecelerateInterpolator()
@@ -74,7 +75,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
     var reactionCount = 11
         set(value) {
             field = value
-            countTextView?.text = value.toString()
+            countTextView?.text = TextUtils.formatCountToShortDecimal(value)
         }
 
 
@@ -101,7 +102,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
         reactionSelector = findViewById(R.id.reactionSelector)
         countTextView = findViewById(R.id.reactionCount)
 
-        countTextView?.text = reactionCount.toString()
+        countTextView?.text = TextUtils.formatCountToShortDecimal(reactionCount)
 
         emojiView?.typeface = this.emojiTypeFace ?: Typeface.DEFAULT
 
@@ -136,6 +137,7 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
         val status = array.getBoolean(R.styleable.ReactionButton_toggled, false)
         setChecked(status)
         setOnClickListener(this)
+        setOnLongClickListener(this)
         array.recycle()
     }
 
@@ -242,40 +244,45 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
      * @param event
      * @return
      */
-    override fun onTouchEvent(event: MotionEvent): Boolean {
-        if (!isEnabled)
-            return true
+//    override fun onTouchEvent(event: MotionEvent): Boolean {
+//        if (!isEnabled)
+//            return true
+//
+//        when (event.action) {
+//            MotionEvent.ACTION_DOWN   ->
+//                /*
+//                Commented out this line and moved the animation effect to the action up event due to
+//                conflicts that were occurring when library is used in sliding type views.
+//
+//                icon.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(DECCELERATE_INTERPOLATOR);
+//                */
+//                isPressed = true
+//
+//            MotionEvent.ACTION_MOVE   -> {
+//                val x = event.x
+//                val y = event.y
+//                val isInside = x > 0 && x < width && y > 0 && y < height
+//                if (isPressed != isInside) {
+//                    isPressed = isInside
+//                }
+//            }
+//
+//            MotionEvent.ACTION_UP     -> {
+//                emojiView!!.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).interpolator = DECCELERATE_INTERPOLATOR
+//                emojiView!!.animate().scaleX(1f).scaleY(1f).interpolator = DECCELERATE_INTERPOLATOR
+//                if (isPressed) {
+//                    performClick()
+//                    isPressed = false
+//                }
+//            }
+//            MotionEvent.ACTION_CANCEL -> isPressed = false
+//        }
+//        return true
+//    }
 
-        when (event.action) {
-            MotionEvent.ACTION_DOWN   ->
-                /*
-                Commented out this line and moved the animation effect to the action up event due to
-                conflicts that were occurring when library is used in sliding type views.
-
-                icon.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).setInterpolator(DECCELERATE_INTERPOLATOR);
-                */
-                isPressed = true
-
-            MotionEvent.ACTION_MOVE   -> {
-                val x = event.x
-                val y = event.y
-                val isInside = x > 0 && x < width && y > 0 && y < height
-                if (isPressed != isInside) {
-                    isPressed = isInside
-                }
-            }
-
-            MotionEvent.ACTION_UP     -> {
-                emojiView!!.animate().scaleX(0.7f).scaleY(0.7f).setDuration(150).interpolator = DECCELERATE_INTERPOLATOR
-                emojiView!!.animate().scaleX(1f).scaleY(1f).interpolator = DECCELERATE_INTERPOLATOR
-                if (isPressed) {
-                    performClick()
-                    isPressed = false
-                }
-            }
-            MotionEvent.ACTION_CANCEL -> isPressed = false
-        }
-        return true
+    override fun onLongClick(v: View?): Boolean {
+        reactedListener?.onLongClick(this)
+        return reactedListener != null
     }
 
     /**
@@ -335,5 +342,6 @@ class ReactionButton @JvmOverloads constructor(context: Context, attrs: Attribut
     interface ReactedListener {
         fun onReacted(reactionButton: ReactionButton)
         fun onUnReacted(reactionButton: ReactionButton)
+        fun onLongClick(reactionButton: ReactionButton)
     }
 }
\ No newline at end of file
diff --git a/vector/src/main/res/layout/bottom_sheet_display_reactions.xml b/vector/src/main/res/layout/bottom_sheet_display_reactions.xml
new file mode 100644
index 0000000000..0f5b63654d
--- /dev/null
+++ b/vector/src/main/res/layout/bottom_sheet_display_reactions.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:minHeight="400dp"
+    android:orientation="vertical">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="44dp"
+        android:gravity="center_vertical"
+        android:padding="8dp"
+        android:text="@string/reactions"
+        android:textColor="?android:textColorSecondary"
+        android:textSize="16sp" />
+
+    <ProgressBar
+        android:id="@+id/bottomSheetViewReactionSpinner"
+        style="?android:attr/progressBarStyleSmall"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_margin="8dp"
+        android:visibility="gone"
+        tools:visibility="visible" />
+
+
+    <com.airbnb.epoxy.EpoxyRecyclerView
+        android:id="@+id/bottom_sheet_display_reactions_list"
+        android:layout_width="match_parent"
+        android:layout_height="0dp"
+        android:layout_weight="1"
+        android:fadeScrollbars="false"
+        android:orientation="vertical"
+        android:scrollbars="vertical"
+        tools:itemCount="15"
+        tools:listitem="@layout/item_simple_reaction_info">
+
+    </com.airbnb.epoxy.EpoxyRecyclerView>
+</LinearLayout>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_simple_reaction_info.xml b/vector/src/main/res/layout/item_simple_reaction_info.xml
new file mode 100644
index 0000000000..0b84aedcd1
--- /dev/null
+++ b/vector/src/main/res/layout/item_simple_reaction_info.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="44dp"
+    android:gravity="center_vertical"
+    android:orientation="horizontal"
+    android:paddingStart="8dp"
+    android:paddingLeft="8dp"
+    android:paddingEnd="8dp">
+
+    <TextView
+        android:id="@+id/itemSimpleReactionInfoKey"
+        android:layout_width="44dp"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:lines="1"
+        android:textColor="?android:textColorPrimary"
+        android:textSize="18sp"
+        tools:text="@sample/reactions.json/data/reaction" />
+
+    <TextView
+        android:id="@+id/itemSimpleReactionInfoMemberName"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="4dp"
+        android:layout_marginLeft="4dp"
+        android:layout_weight="1"
+        android:ellipsize="end"
+        android:lines="1"
+        android:textColor="?android:textColorPrimary"
+        android:textSize="16sp"
+        tools:text="@sample/matrix.json/data/displayName" />
+
+    <TextView
+        android:id="@+id/itemSimpleReactionInfoTime"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:lines="1"
+        android:textColor="?android:textColorSecondary"
+        android:textSize="12sp"
+        tools:text="10:44" />
+
+
+</LinearLayout>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/reaction_button.xml b/vector/src/main/res/layout/reaction_button.xml
index aca0d2bf7d..6c16929788 100644
--- a/vector/src/main/res/layout/reaction_button.xml
+++ b/vector/src/main/res/layout/reaction_button.xml
@@ -2,16 +2,19 @@
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="44dp"
+    android:id="@+id/reactionSelector"
+    android:layout_width="wrap_content"
+    android:minWidth="44dp"
     android:layout_height="26dp"
+    android:background="@drawable/rounded_rect_shape"
     android:clipChildren="false">
 
 
-    <View
-        android:id="@+id/reactionSelector"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="@drawable/rounded_rect_shape" />
+    <!--<View-->
+        <!--android:id="@+id/reactionSelector"-->
+        <!--android:layout_width="match_parent"-->
+        <!--android:layout_height="match_parent"-->
+        <!--android:background="@drawable/rounded_rect_shape" />-->
 
     <im.vector.riotredesign.features.reactions.widget.DotsView
         android:id="@+id/dots"
@@ -42,17 +45,23 @@
         android:gravity="center"
         android:textColor="@color/black"
         android:textSize="13sp"
+        app:layout_constraintHorizontal_chainStyle="packed"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/reactionCount"
         tools:text="👍" />
 
     <TextView
         android:id="@+id/reactionCount"
-        android:layout_width="0dp"
-        android:layout_height="match_parent"
-        android:layout_marginEnd="6dp"
-        android:layout_marginRight="6dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        app:layout_constraintHorizontal_chainStyle="packed"
+        app:layout_constraintBaseline_toBaselineOf="@id/reactionText"
+        android:layout_marginStart="-4dp"
+        android:layout_marginLeft="-4dp"
+        android:layout_marginEnd="8dp"
+        android:layout_marginRight="8dp"
         android:gravity="center"
         android:maxLines="1"
         android:textColor="?riotx_text_secondary"
@@ -61,7 +70,8 @@
         app:autoSizeMaxTextSize="14sp"
         app:autoSizeMinTextSize="8sp"
         app:autoSizeTextType="uniform"
+        app:layout_constraintStart_toEndOf="@id/reactionText"
         app:layout_constraintEnd_toEndOf="parent"
-        tools:text="10" />
+        tools:text="13450" />
 
 </androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml
index 3554d61948..8109c89d10 100644
--- a/vector/src/main/res/values/strings_riotX.xml
+++ b/vector/src/main/res/values/strings_riotX.xml
@@ -20,6 +20,7 @@
     <string name="reactions_agree">Agree</string>
     <string name="reactions_like">Like</string>
     <string name="message_add_reaction">Add Reaction</string>
+    <string name="reactions">Reactions</string>
 
     <string name="event_redacted_by_user_reason">Event deleted by user</string>
     <string name="event_redacted_by_admin_reason">Event moderated by room admin</string>

From 297f202005100d79fd8478e9c8431b6075728350 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Wed, 5 Jun 2019 23:42:08 +0200
Subject: [PATCH 05/18] Fix / Local echo taking too much time

---
 .../internal/session/room/EventRelationsAggregationTask.kt     | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt
index a66b65ab71..e5fb5493bf 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt
@@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.database.query.create
 import im.vector.matrix.android.internal.database.query.where
 import im.vector.matrix.android.internal.task.Task
 import im.vector.matrix.android.internal.util.tryTransactionAsync
+import im.vector.matrix.android.internal.util.tryTransactionSync
 import io.realm.Realm
 import timber.log.Timber
 
@@ -48,7 +49,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
     private val SHOULD_HANDLE_SERVER_AGREGGATION = false
 
     override fun execute(params: EventRelationsAggregationTask.Params): Try<Unit> {
-        return monarchy.tryTransactionAsync { realm ->
+        return monarchy.tryTransactionSync { realm ->
             update(realm, params.events, params.userId)
         }
     }

From e22b555b58f5a79ed5669a9d64a3bf4b346078e4 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Wed, 5 Jun 2019 23:45:46 +0200
Subject: [PATCH 06/18] Refactoring (duplication in Message Item Factory) +
 cleaning

---
 .../timeline/TimelineEventController.kt       |  9 ++-
 .../timeline/action/ViewReactionViewModel.kt  | 81 +++++++++----------
 .../timeline/factory/MessageItemFactory.kt    | 68 ++--------------
 .../detail/timeline/item/AbsMessageItem.kt    | 19 +++--
 .../res/layout/item_simple_reaction_info.xml  |  4 +-
 5 files changed, 69 insertions(+), 112 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
index 175702cfa8..2861ae6a70 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
@@ -46,7 +46,7 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
                               private val backgroundHandler: Handler = TimelineAsyncHelper.getBackgroundHandler()
 ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
 
-    interface Callback : ReactionPillCallback {
+    interface Callback : ReactionPillCallback, AvatarCallback {
         fun onEventVisible(event: TimelineEvent)
         fun onUrlClicked(url: String)
         fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View)
@@ -55,8 +55,6 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
         fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
         fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View)
         fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean
-        fun onAvatarClicked(informationData: MessageInformationData)
-        fun onMemberNameClicked(informationData: MessageInformationData)
         fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?)
     }
 
@@ -65,6 +63,11 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
         fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String)
     }
 
+    interface AvatarCallback {
+        fun onAvatarClicked(informationData: MessageInformationData)
+        fun onMemberNameClicked(informationData: MessageInformationData)
+    }
+
     private val collapsedEventIds = linkedSetOf<String>()
     private val mergeItemCollapseStates = HashMap<String, Boolean>()
     private val modelCache = arrayListOf<CacheItemData?>()
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt
index a5e7fdd882..8854886512 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionViewModel.kt
@@ -1,16 +1,12 @@
 package im.vector.riotredesign.features.home.room.detail.timeline.action
 
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LiveData
+import androidx.fragment.app.Fragment
 import androidx.lifecycle.Observer
 import com.airbnb.mvrx.*
 import im.vector.matrix.android.api.session.Session
-import im.vector.matrix.android.api.session.room.model.EventAnnotationsSummary
 import im.vector.riotredesign.core.extensions.localDateTime
 import im.vector.riotredesign.core.platform.VectorViewModel
 import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.launch
 import org.koin.android.ext.android.get
 
 
@@ -33,53 +29,41 @@ data class ReactionInfo(
  */
 class ViewReactionViewModel(private val session: Session,
                             private val timelineDateFormatter: TimelineDateFormatter,
-                            lifecycleOwner: LifecycleOwner?,
-                            liveSummary: LiveData<List<EventAnnotationsSummary>>?,
                             initialState: DisplayReactionsViewState) : VectorViewModel<DisplayReactionsViewState>(initialState) {
 
     init {
         loadReaction()
-        if (lifecycleOwner != null) {
-            liveSummary?.observe(lifecycleOwner, Observer {
-                it?.firstOrNull()?.let {
-                    loadReaction()
-                }
-            })
-        }
-
     }
 
-    private fun loadReaction() = withState { state ->
+    fun loadReaction() = withState { state ->
 
-        GlobalScope.launch {
-            try {
-                val room = session.getRoom(state.roomId)
-                val event = room?.getTimeLineEvent(state.eventId)
-                if (event == null) {
-                    setState { copy(mapReactionKeyToMemberList = Fail(Throwable())) }
-                    return@launch
-                }
-                var results = ArrayList<ReactionInfo>()
-                event.annotations?.reactionsSummary?.forEach { sum ->
+        try {
+            val room = session.getRoom(state.roomId)
+            val event = room?.getTimeLineEvent(state.eventId)
+            if (event == null) {
+                setState { copy(mapReactionKeyToMemberList = Fail(Throwable())) }
+                return@withState
+            }
+            var results = ArrayList<ReactionInfo>()
+            event.annotations?.reactionsSummary?.forEach { sum ->
 
-                    sum.sourceEvents.mapNotNull { room.getTimeLineEvent(it) }.forEach {
-                        val localDate = it.root.localDateTime()
-                        results.add(ReactionInfo(it.root.eventId!!, sum.key, it.root.sender
-                                ?: "", it.senderName, timelineDateFormatter.formatMessageHour(localDate)))
-                    }
-                }
-                setState {
-                    copy(
-                            mapReactionKeyToMemberList = Success(results.sortedBy { it.timestamp })
-                    )
-                }
-            } catch (t: Throwable) {
-                setState {
-                    copy(
-                            mapReactionKeyToMemberList = Fail(t)
-                    )
+                sum.sourceEvents.mapNotNull { room.getTimeLineEvent(it) }.forEach {
+                    val localDate = it.root.localDateTime()
+                    results.add(ReactionInfo(it.root.eventId!!, sum.key, it.root.sender
+                            ?: "", it.senderName, timelineDateFormatter.formatMessageHour(localDate)))
                 }
             }
+            setState {
+                copy(
+                        mapReactionKeyToMemberList = Success(results.sortedBy { it.timestamp })
+                )
+            }
+        } catch (t: Throwable) {
+            setState {
+                copy(
+                        mapReactionKeyToMemberList = Fail(t)
+                )
+            }
         }
     }
 
@@ -98,7 +82,18 @@ class ViewReactionViewModel(private val session: Session,
         override fun create(viewModelContext: ViewModelContext, state: DisplayReactionsViewState): ViewReactionViewModel? {
             val session = viewModelContext.activity.get<Session>()
             val eventId = (viewModelContext.args as TimelineEventFragmentArgs).eventId
-            return ViewReactionViewModel(session, viewModelContext.activity.get(), viewModelContext.activity, session.getRoom(state.roomId)?.getEventSummaryLive(eventId), state)
+            val lifecycleOwner = (viewModelContext as FragmentViewModelContext).fragment<Fragment>()
+            val liveSummary = session.getRoom(state.roomId)?.getEventSummaryLive(eventId)
+            val viewReactionViewModel = ViewReactionViewModel(session, viewModelContext.activity.get(), state)
+            // This states observes the live summary
+            // When fragment context will be destroyed the observer will automatically removed
+            liveSummary?.observe(lifecycleOwner, Observer {
+                it?.firstOrNull()?.let {
+                    viewReactionViewModel.loadReaction()
+                }
+            })
+
+            return viewReactionViewModel
         }
 
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
index 7571fead47..3390f4c23e 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/MessageItemFactory.kt
@@ -143,18 +143,11 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                                       callback: TimelineEventController.Callback?): MessageFileItem? {
         return MessageFileItem_()
                 .informationData(informationData)
+                .avatarCallback(callback)
                 .filename(messageContent.body)
                 .iconRes(R.drawable.filetype_audio)
                 .reactionPillCallback(callback)
                 .emojiTypeFace(emojiCompatFontProvider.typeface)
-                .avatarClickListener(
-                        DebouncedClickListener(View.OnClickListener {
-                            callback?.onAvatarClicked(informationData)
-                        }))
-                .memberClickListener(
-                        DebouncedClickListener(View.OnClickListener {
-                            callback?.onMemberNameClicked(informationData)
-                        }))
                 .cellClickListener(
                         DebouncedClickListener(View.OnClickListener { view: View ->
                             callback?.onEventCellClicked(informationData, messageContent, view)
@@ -174,18 +167,11 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                                      callback: TimelineEventController.Callback?): MessageFileItem? {
         return MessageFileItem_()
                 .informationData(informationData)
+                .avatarCallback(callback)
                 .filename(messageContent.body)
                 .reactionPillCallback(callback)
                 .emojiTypeFace(emojiCompatFontProvider.typeface)
                 .iconRes(R.drawable.filetype_attachment)
-                .avatarClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onAvatarClicked(informationData)
-                        }))
-                .memberClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onMemberNameClicked(informationData)
-                        }))
                 .cellClickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
                             callback?.onEventCellClicked(informationData, messageContent, view)
@@ -223,17 +209,10 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
         return MessageImageVideoItem_()
                 .playable(messageContent.info?.mimeType == "image/gif")
                 .informationData(informationData)
+                .avatarCallback(callback)
                 .mediaData(data)
                 .reactionPillCallback(callback)
                 .emojiTypeFace(emojiCompatFontProvider.typeface)
-                .avatarClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onAvatarClicked(informationData)
-                        }))
-                .memberClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onMemberNameClicked(informationData)
-                        }))
                 .clickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
                             callback?.onImageMessageClicked(messageContent, data, view)
@@ -271,17 +250,10 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
         return MessageImageVideoItem_()
                 .playable(true)
                 .informationData(informationData)
+                .avatarCallback(callback)
                 .mediaData(thumbnailData)
                 .reactionPillCallback(callback)
                 .emojiTypeFace(emojiCompatFontProvider.typeface)
-                .avatarClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onAvatarClicked(informationData)
-                        }))
-                .memberClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onMemberNameClicked(informationData)
-                        }))
                 .cellClickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
                             callback?.onEventCellClicked(informationData, messageContent, view)
@@ -316,16 +288,9 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                     }
                 }
                 .informationData(informationData)
+                .avatarCallback(callback)
                 .reactionPillCallback(callback)
                 .emojiTypeFace(emojiCompatFontProvider.typeface)
-                .avatarClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onAvatarClicked(informationData)
-                        }))
-                .memberClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onMemberNameClicked(informationData)
-                        }))
                 //click on the text
                 .clickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
@@ -390,12 +355,9 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
         return MessageTextItem_()
                 .message(message)
                 .informationData(informationData)
+                .avatarCallback(callback)
                 .reactionPillCallback(callback)
                 .emojiTypeFace(emojiCompatFontProvider.typeface)
-                .avatarClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onAvatarClicked(informationData)
-                        }))
                 .memberClickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
                             callback?.onMemberNameClicked(informationData)
@@ -430,16 +392,9 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                     }
                 }
                 .informationData(informationData)
+                .avatarCallback(callback)
                 .reactionPillCallback(callback)
                 .emojiTypeFace(emojiCompatFontProvider.typeface)
-                .avatarClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onAvatarClicked(informationData)
-                        }))
-                .memberClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onMemberNameClicked(informationData)
-                        }))
                 .cellClickListener(
                         DebouncedClickListener(View.OnClickListener { view ->
                             callback?.onEventCellClicked(informationData, messageContent, view)
@@ -454,14 +409,7 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
                                   callback: TimelineEventController.Callback?): RedactedMessageItem? {
         return RedactedMessageItem_()
                 .informationData(informationData)
-                .avatarClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onAvatarClicked(informationData)
-                        }))
-                .memberClickListener(
-                        DebouncedClickListener(View.OnClickListener { view ->
-                            callback?.onMemberNameClicked(informationData)
-                        }))
+                .avatarCallback(callback)
     }
 
     private fun linkifyBody(body: CharSequence, callback: TimelineEventController.Callback?): CharSequence {
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt
index a3f43baf51..7749e152b3 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/AbsMessageItem.kt
@@ -29,6 +29,7 @@ import androidx.core.view.isGone
 import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
 import im.vector.riotredesign.R
+import im.vector.riotredesign.core.utils.DebouncedClickListener
 import im.vector.riotredesign.core.utils.DimensionUtils.dpToPx
 import im.vector.riotredesign.features.home.AvatarRenderer
 import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
@@ -45,9 +46,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
     @EpoxyAttribute
     var cellClickListener: View.OnClickListener? = null
 
-    @EpoxyAttribute
-    var avatarClickListener: View.OnClickListener? = null
-
     @EpoxyAttribute
     var memberClickListener: View.OnClickListener? = null
 
@@ -57,6 +55,17 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
     @EpoxyAttribute
     var reactionPillCallback: TimelineEventController.ReactionPillCallback? = null
 
+    @EpoxyAttribute
+    var avatarCallback: TimelineEventController.AvatarCallback?= null
+
+    private val _avatarClickListener =  DebouncedClickListener(View.OnClickListener {
+        avatarCallback?.onAvatarClicked(informationData)
+    })
+    private val _memberNameClickListener =  DebouncedClickListener(View.OnClickListener {
+        avatarCallback?.onMemberNameClicked(informationData)
+    })
+
+
     var reactionClickListener: ReactionButton.ReactedListener = object : ReactionButton.ReactedListener {
         override fun onReacted(reactionButton: ReactionButton) {
             reactionPillCallback?.onClickOnReactionPill(informationData, reactionButton.reactionString, true)
@@ -81,9 +90,9 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
                 width = size
             }
             holder.avatarImageView.visibility = View.VISIBLE
-            holder.avatarImageView.setOnClickListener(avatarClickListener)
+            holder.avatarImageView.setOnClickListener(_avatarClickListener)
             holder.memberNameView.visibility = View.VISIBLE
-            holder.memberNameView.setOnClickListener(memberClickListener)
+            holder.memberNameView.setOnClickListener(_memberNameClickListener)
             holder.timeView.visibility = View.VISIBLE
             holder.timeView.text = informationData.time
             holder.memberNameView.text = informationData.memberName
diff --git a/vector/src/main/res/layout/item_simple_reaction_info.xml b/vector/src/main/res/layout/item_simple_reaction_info.xml
index 0b84aedcd1..0458b17126 100644
--- a/vector/src/main/res/layout/item_simple_reaction_info.xml
+++ b/vector/src/main/res/layout/item_simple_reaction_info.xml
@@ -11,8 +11,10 @@
 
     <TextView
         android:id="@+id/itemSimpleReactionInfoKey"
-        android:layout_width="44dp"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:layout_marginRight="8dp"
         android:gravity="center"
         android:lines="1"
         android:textColor="?android:textColorPrimary"

From 834a865dfa4d5717681978070609e54081b23a5c Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Thu, 6 Jun 2019 11:54:08 +0200
Subject: [PATCH 07/18] Show text with only few emojis in bigger

---
 .../riotredesign/core/utils/DimensionUtils.kt |  8 +++
 .../vector/riotredesign/core/utils/Emoji.kt   | 69 +++++++++++++++++++
 .../detail/timeline/item/MessageTextItem.kt   | 18 +++--
 3 files changed, 90 insertions(+), 5 deletions(-)
 create mode 100644 vector/src/main/java/im/vector/riotredesign/core/utils/Emoji.kt

diff --git a/vector/src/main/java/im/vector/riotredesign/core/utils/DimensionUtils.kt b/vector/src/main/java/im/vector/riotredesign/core/utils/DimensionUtils.kt
index ab3654a33e..44659da6af 100644
--- a/vector/src/main/java/im/vector/riotredesign/core/utils/DimensionUtils.kt
+++ b/vector/src/main/java/im/vector/riotredesign/core/utils/DimensionUtils.kt
@@ -28,4 +28,12 @@ object DimensionUtils {
                 context.resources.displayMetrics
         ).toInt()
     }
+
+    fun spToPx(sp: Int, context: Context): Int {
+        return TypedValue.applyDimension(
+                TypedValue.COMPLEX_UNIT_SP,
+                sp.toFloat(),
+                context.resources.displayMetrics
+        ).toInt()
+    }
 }
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/core/utils/Emoji.kt b/vector/src/main/java/im/vector/riotredesign/core/utils/Emoji.kt
new file mode 100644
index 0000000000..3595ad876e
--- /dev/null
+++ b/vector/src/main/java/im/vector/riotredesign/core/utils/Emoji.kt
@@ -0,0 +1,69 @@
+package im.vector.riotredesign.core.utils
+
+import java.util.regex.Pattern
+
+private val emojisPattern = Pattern.compile("((?:[\uD83C\uDF00-\uD83D\uDDFF]" +
+        "|[\uD83E\uDD00-\uD83E\uDDFF]" +
+        "|[\uD83D\uDE00-\uD83D\uDE4F]" +
+        "|[\uD83D\uDE80-\uD83D\uDEFF]" +
+        "|[\u2600-\u26FF]\uFE0F?" +
+        "|[\u2700-\u27BF]\uFE0F?" +
+        "|\u24C2\uFE0F?" +
+        "|[\uD83C\uDDE6-\uD83C\uDDFF]{1,2}" +
+        "|[\uD83C\uDD70\uD83C\uDD71\uD83C\uDD7E\uD83C\uDD7F\uD83C\uDD8E\uD83C\uDD91-\uD83C\uDD9A]\uFE0F?" +
+        "|[\u0023\u002A\u0030-\u0039]\uFE0F?\u20E3" +
+        "|[\u2194-\u2199\u21A9-\u21AA]\uFE0F?" +
+        "|[\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55]\uFE0F?" +
+        "|[\u2934\u2935]\uFE0F?" +
+        "|[\u3030\u303D]\uFE0F?" +
+        "|[\u3297\u3299]\uFE0F?" +
+        "|[\uD83C\uDE01\uD83C\uDE02\uD83C\uDE1A\uD83C\uDE2F\uD83C\uDE32-\uD83C\uDE3A\uD83C\uDE50\uD83C\uDE51]\uFE0F?" +
+        "|[\u203C\u2049]\uFE0F?" +
+        "|[\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE]\uFE0F?" +
+        "|[\u00A9\u00AE]\uFE0F?" +
+        "|[\u2122\u2139]\uFE0F?" +
+        "|\uD83C\uDC04\uFE0F?" +
+        "|\uD83C\uDCCF\uFE0F?" +
+        "|[\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA]\uFE0F?))")
+
+/**
+ * Test if a string contains emojis.
+ * It seems that the regex [emoji_regex]+ does not work.
+ * Some characters like ?, # or digit are accepted.
+ *
+ * @param str the body to test
+ * @return true if the body contains only emojis
+ */
+fun containsOnlyEmojis(str: String?): Boolean {
+    var res = false
+
+    if (str != null && str.isNotEmpty()) {
+        val matcher = emojisPattern.matcher(str)
+
+        var start = -1
+        var end = -1
+
+        while (matcher.find()) {
+            val nextStart = matcher.start()
+
+            // first emoji position
+            if (start < 0) {
+                if (nextStart > 0) {
+                    return false
+                }
+            } else {
+                // must not have a character between
+                if (nextStart != end) {
+                    return false
+                }
+            }
+            start = nextStart
+            end = matcher.end()
+        }
+
+        res = -1 != start && end == str.length
+    }
+
+    return res
+}
+
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt
index 790243c4d6..b6f779011a 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/MessageTextItem.kt
@@ -16,10 +16,7 @@
 
 package im.vector.riotredesign.features.home.room.detail.timeline.item
 
-import android.text.Spannable
 import android.view.View
-import android.widget.ImageView
-import android.widget.TextView
 import androidx.appcompat.widget.AppCompatTextView
 import androidx.core.text.PrecomputedTextCompat
 import androidx.core.text.toSpannable
@@ -28,6 +25,8 @@ import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
 import im.vector.matrix.android.api.permalinks.MatrixLinkify
 import im.vector.riotredesign.R
+import im.vector.riotredesign.core.utils.DimensionUtils
+import im.vector.riotredesign.core.utils.containsOnlyEmojis
 import im.vector.riotredesign.features.html.PillImageSpan
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.GlobalScope
@@ -47,12 +46,21 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
     override fun bind(holder: Holder) {
         super.bind(holder)
         MatrixLinkify.addLinkMovementMethod(holder.messageView)
-        val textFuture = PrecomputedTextCompat.getTextFuture(message ?: "",
+
+        val msg = message ?: ""
+        if (msg.length <= 4 && containsOnlyEmojis(msg.toString())) {
+            holder.messageView.textSize = 44F
+        } else {
+            holder.messageView.textSize = 14F
+        }
+
+        val textFuture = PrecomputedTextCompat.getTextFuture(msg,
                 TextViewCompat.getTextMetricsParams(holder.messageView),
                 null)
+
         holder.messageView.setTextFuture(textFuture)
         holder.messageView.renderSendState()
-        holder.messageView.setOnClickListener (clickListener)
+        holder.messageView.setOnClickListener(clickListener)
         holder.messageView.setOnLongClickListener(longClickListener)
         findPillsAndProcess { it.bind(holder.messageView) }
     }

From 053dc1d8dda281c1adccc0e32070e91f870b3f58 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Thu, 6 Jun 2019 11:55:26 +0200
Subject: [PATCH 08/18] Show 'view reaction' option in context menu

---
 .../home/room/detail/RoomDetailFragment.kt    |  5 +++
 .../home/room/detail/RoomDetailViewModel.kt   |  6 +--
 .../timeline/action/MessageMenuViewModel.kt   | 27 ++++++++-----
 .../main/res/drawable/ic_view_reactions.xml   | 38 +++++++++++++++++++
 .../main/res/drawable/rounded_rect_shape.xml  |  2 +-
 vector/src/main/res/values/strings_riotX.xml  |  1 +
 6 files changed, 66 insertions(+), 13 deletions(-)
 create mode 100644 vector/src/main/res/drawable/ic_view_reactions.xml

diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
index 5ab0e46c94..3d3667b3ea 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
@@ -621,6 +621,11 @@ class RoomDetailFragment :
                     val eventId = actionData.data?.toString() ?: return
                     startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE)
                 }
+                MessageMenuViewModel.ACTION_VIEW_REACTIONS -> {
+                    val messageInformationData = actionData.data as? MessageInformationData ?: return
+                    ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId,messageInformationData)
+                            .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
+                }
                 MessageMenuViewModel.ACTION_COPY           -> {
                     //I need info about the current selected message :/
                     copyToClipboard(requireContext(), actionData.data?.toString() ?: "", false)
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
index 7a47cff0f6..351129a2f1 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
@@ -195,7 +195,8 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
                     }
                 }
                 SendMode.EDIT    -> {
-                    room.editTextMessage(state.selectedEvent?.root?.eventId ?: "", action.text, action.autoMarkdown)
+                    room.editTextMessage(state.selectedEvent?.root?.eventId
+                            ?: "", action.text, action.autoMarkdown)
                     setState {
                         copy(
                                 sendMode = SendMode.REGULAR,
@@ -330,7 +331,6 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
         room.updateQuickReaction(action.selectedReaction, action.opposite, action.targetEventId, session.sessionParams.credentials.userId)
     }
 
-
     private fun handleSendMedia(action: RoomDetailActions.SendMedia) {
         val attachments = action.mediaFiles.map {
             ContentAttachmentData(
@@ -352,7 +352,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
         displayedEventsObservable.accept(action)
         //We need to update this with the related m.replace also (to move read receipt)
         action.event.annotations?.editSummary?.sourceEvents?.forEach {
-            room.getTimeLineEvent(it)?.let {event ->
+            room.getTimeLineEvent(it)?.let { event ->
                 displayedEventsObservable.accept(RoomDetailActions.EventDisplayed(event))
             }
         }
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
index 776be95042..6a77c97062 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
@@ -50,7 +50,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
             val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
                     ?: return null
 
-            val messageContent: MessageContent =  event.annotations?.editSummary?.aggregatedContent?.toModel()
+            val messageContent: MessageContent = event.annotations?.editSummary?.aggregatedContent?.toModel()
                     ?: event.root.content.toModel() ?: return null
             val type = messageContent.type
 
@@ -64,9 +64,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                         )
                 )
             }
-
-
-            //TODO determine if can copy, forward, reply, quote, report?
+            
             val actions = ArrayList<SimpleAction>().apply {
 
                 if (event.sendState == SendState.SENDING) {
@@ -94,10 +92,13 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                 }
 
                 if (canQuote(event, messageContent)) {
-                    //TODO quote icon
                     this.add(SimpleAction(ACTION_QUOTE, R.string.quote, R.drawable.ic_quote, parcel.eventId))
                 }
 
+                if (canViewReactions(event)) {
+                    this.add(SimpleAction(ACTION_VIEW_REACTIONS, R.string.message_view_reaction, R.drawable.ic_view_reactions, parcel.informationData))
+                }
+
                 if (canShare(type)) {
                     if (messageContent is MessageImageContent) {
                         this.add(
@@ -144,7 +145,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                 MessageType.MSGTYPE_VIDEO,
                 MessageType.MSGTYPE_AUDIO,
                 MessageType.MSGTYPE_FILE -> true
-                else -> false
+                else                     -> false
             }
         }
 
@@ -159,7 +160,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                 MessageType.MSGTYPE_LOCATION -> {
                     true
                 }
-                else -> false
+                else                         -> false
             }
         }
 
@@ -170,6 +171,13 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
             return event.root.sender == myUserId
         }
 
+        private fun canViewReactions(event: TimelineEvent): Boolean {
+            //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
+            if (event.root.type != EventType.MESSAGE) return false
+            //TODO if user is admin or moderator
+            return event.annotations?.reactionsSummary?.isNotEmpty() ?: false
+        }
+
         private fun canEdit(event: TimelineEvent, myUserId: String): Boolean {
             //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
             if (event.root.type != EventType.MESSAGE) return false
@@ -191,7 +199,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                 MessageType.MSGTYPE_LOCATION -> {
                     true
                 }
-                else -> false
+                else                         -> false
             }
         }
 
@@ -203,7 +211,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                 MessageType.MSGTYPE_VIDEO -> {
                     true
                 }
-                else -> false
+                else                      -> false
             }
         }
 
@@ -220,6 +228,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
         const val PERMALINK = "PERMALINK"
         const val ACTION_FLAG = "ACTION_FLAG"
         const val ACTION_QUICK_REACT = "ACTION_QUICK_REACT"
+        const val ACTION_VIEW_REACTIONS = "ACTION_VIEW_REACTIONS"
 
 
     }
diff --git a/vector/src/main/res/drawable/ic_view_reactions.xml b/vector/src/main/res/drawable/ic_view_reactions.xml
new file mode 100644
index 0000000000..f4106852b8
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_view_reactions.xml
@@ -0,0 +1,38 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="22dp"
+    android:height="22dp"
+    android:viewportWidth="22"
+    android:viewportHeight="22">
+  <path
+      android:pathData="M11,11m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#9e9e9e"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m7,13s1.5,2 4,2 4,-2 4,-2"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#9e9e9e"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m8,8h0.01"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#9e9e9e"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="m14,8h0.01"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#9e9e9e"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/vector/src/main/res/drawable/rounded_rect_shape.xml b/vector/src/main/res/drawable/rounded_rect_shape.xml
index cf083254f3..e9c0a27f59 100644
--- a/vector/src/main/res/drawable/rounded_rect_shape.xml
+++ b/vector/src/main/res/drawable/rounded_rect_shape.xml
@@ -2,7 +2,7 @@
 <shape xmlns:android="http://schemas.android.com/apk/res/android"
     android:shape="rectangle">
 
-    <size android:width="40dp" android:height="22dp"/>
+    <!--<size android:width="40dp" android:height="22dp"/>-->
 
     <solid android:color="?vctr_list_header_background_color" />
 
diff --git a/vector/src/main/res/values/strings_riotX.xml b/vector/src/main/res/values/strings_riotX.xml
index 8109c89d10..8d39ffe774 100644
--- a/vector/src/main/res/values/strings_riotX.xml
+++ b/vector/src/main/res/values/strings_riotX.xml
@@ -20,6 +20,7 @@
     <string name="reactions_agree">Agree</string>
     <string name="reactions_like">Like</string>
     <string name="message_add_reaction">Add Reaction</string>
+    <string name="message_view_reaction">View Reactions</string>
     <string name="reactions">Reactions</string>
 
     <string name="event_redacted_by_user_reason">Event deleted by user</string>

From 04576ba7fdef0fb7425c3512cdb5f5312ce9d203 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Thu, 6 Jun 2019 12:26:38 +0200
Subject: [PATCH 09/18] Permalink message action + Fix crash on injection of
 navigator

---
 .../vector/riotredesign/core/di/AppModule.kt  |  5 ++--
 .../features/home/HomeDrawerFragment.kt       |  2 +-
 .../features/home/HomeNavigator.kt            |  2 +-
 .../home/room/detail/RoomDetailFragment.kt    | 18 ++++++++----
 .../timeline/action/MessageMenuViewModel.kt   |  6 ++--
 .../home/room/list/RoomListFragment.kt        |  4 +--
 .../features/navigation/DefaultNavigator.kt   | 28 +++++++++----------
 .../features/navigation/Navigator.kt          |  9 +++---
 .../roomdirectory/PublicRoomsFragment.kt      |  4 +--
 .../RoomPreviewNoPreviewFragment.kt           |  2 +-
 10 files changed, 44 insertions(+), 36 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt
index 542c0953e7..28ae4e1a46 100644
--- a/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt
+++ b/vector/src/main/java/im/vector/riotredesign/core/di/AppModule.kt
@@ -18,7 +18,6 @@ package im.vector.riotredesign.core.di
 
 import android.content.Context
 import android.content.Context.MODE_PRIVATE
-import androidx.fragment.app.Fragment
 import im.vector.matrix.android.api.Matrix
 import im.vector.riotredesign.EmojiCompatFontProvider
 import im.vector.riotredesign.core.error.ErrorFormatter
@@ -87,8 +86,8 @@ class AppModule(private val context: Context) {
             Matrix.getInstance().currentSession!!
         }
 
-        factory { (fragment: Fragment) ->
-            DefaultNavigator(fragment) as Navigator
+        factory {
+            DefaultNavigator() as Navigator
         }
 
         single {
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt
index ff08720d44..97baa539b9 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt
@@ -54,7 +54,7 @@ class HomeDrawerFragment : VectorBaseFragment() {
             }
         }
         homeDrawerHeaderSettingsView.setOnClickListener {
-            navigator.openSettings()
+            navigator.openSettings(requireActivity())
         }
 
         // Debug menu
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt
index 8d29c0ab8e..e46fd7f59e 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeNavigator.kt
@@ -48,7 +48,7 @@ class HomeNavigator {
         activity?.let {
             //TODO enable eventId permalink. It doesn't work enough at the moment.
             it.drawerLayout?.closeDrawer(GravityCompat.START)
-            navigator.openRoom(roomId)
+            navigator.openRoom(roomId, it)
         }
     }
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
index 3d3667b3ea..1da6ed9d13 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
@@ -53,6 +53,7 @@ import com.jaiselrahman.filepicker.model.MediaFile
 import com.otaliastudios.autocomplete.Autocomplete
 import com.otaliastudios.autocomplete.AutocompleteCallback
 import com.otaliastudios.autocomplete.CharPolicy
+import im.vector.matrix.android.api.permalinks.PermalinkFactory
 import im.vector.matrix.android.api.session.Session
 import im.vector.matrix.android.api.session.events.model.toModel
 import im.vector.matrix.android.api.session.room.model.EditAggregatedSummary
@@ -622,16 +623,16 @@ class RoomDetailFragment :
                     startActivityForResult(EmojiReactionPickerActivity.intent(requireContext(), eventId), REACTION_SELECT_REQUEST_CODE)
                 }
                 MessageMenuViewModel.ACTION_VIEW_REACTIONS -> {
-                    val messageInformationData = actionData.data as? MessageInformationData ?: return
-                    ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId,messageInformationData)
+                    val messageInformationData = actionData.data as? MessageInformationData
+                            ?: return
+                    ViewReactionBottomSheet.newInstance(roomDetailArgs.roomId, messageInformationData)
                             .show(requireActivity().supportFragmentManager, "DISPLAY_REACTIONS")
                 }
                 MessageMenuViewModel.ACTION_COPY           -> {
                     //I need info about the current selected message :/
                     copyToClipboard(requireContext(), actionData.data?.toString() ?: "", false)
-                    val snack = Snackbar.make(view!!, requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
-                    snack.view.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.notification_accent_color))
-                    snack.show()
+                    val msg = requireContext().getString(R.string.copied_to_clipboard)
+                    showSnackWithMessage(msg, Snackbar.LENGTH_SHORT)
                 }
                 MessageMenuViewModel.ACTION_DELETE         -> {
                     val eventId = actionData.data?.toString() ?: return
@@ -698,6 +699,13 @@ class RoomDetailFragment :
                     val eventId = actionData.data.toString()
                     roomDetailViewModel.process(RoomDetailActions.EnterReplyMode(eventId))
                 }
+                MessageMenuViewModel.ACTION_COPY_PERMALINK -> {
+                    val eventId = actionData.data.toString()
+                    val permalink = PermalinkFactory.createPermalink(roomDetailArgs.roomId, eventId)
+                    copyToClipboard(requireContext(), permalink, false)
+                    showSnackWithMessage(requireContext().getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
+
+                }
                 else                                       -> {
                     Toast.makeText(context, "Action ${actionData.actionId} not implemented", Toast.LENGTH_LONG).show()
                 }
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
index 6a77c97062..ca422f720c 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
@@ -64,7 +64,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                         )
                 )
             }
-            
+
             val actions = ArrayList<SimpleAction>().apply {
 
                 if (event.sendState == SendState.SENDING) {
@@ -123,7 +123,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                 if (event.isEncrypted()) {
                     this.add(SimpleAction(VIEW_DECRYPTED_SOURCE, R.string.view_decrypted_source, R.drawable.ic_view_source, parcel.eventId))
                 }
-                this.add(SimpleAction(PERMALINK, R.string.permalink, R.drawable.ic_permalink, parcel.eventId))
+                this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, parcel.eventId))
 
                 if (currentSession.sessionParams.credentials.userId != event.root.sender) {
                     //not sent by me
@@ -225,7 +225,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
         const val ACTION_DELETE = "delete"
         const val VIEW_SOURCE = "VIEW_SOURCE"
         const val VIEW_DECRYPTED_SOURCE = "VIEW_DECRYPTED_SOURCE"
-        const val PERMALINK = "PERMALINK"
+        const val ACTION_COPY_PERMALINK = "ACTION_COPY_PERMALINK"
         const val ACTION_FLAG = "ACTION_FLAG"
         const val ACTION_QUICK_REACT = "ACTION_QUICK_REACT"
         const val ACTION_VIEW_REACTIONS = "ACTION_VIEW_REACTIONS"
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt
index 247ab070b5..e354be2f74 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/list/RoomListFragment.kt
@@ -72,7 +72,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
         setupRecyclerView()
         roomListViewModel.subscribe { renderState(it) }
         roomListViewModel.openRoomLiveData.observeEvent(this) {
-            navigator.openRoom(it)
+            navigator.openRoom(it, requireActivity())
         }
 
         createChatFabMenu.listener = this
@@ -116,7 +116,7 @@ class RoomListFragment : VectorBaseFragment(), RoomSummaryController.Callback, O
 
 
     override fun openRoomDirectory() {
-        navigator.openRoomDirectory()
+        navigator.openRoomDirectory(requireActivity())
     }
 
     override fun createDirectChat() {
diff --git a/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt
index 307d4fe812..00207950ba 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt
@@ -17,6 +17,7 @@
 package im.vector.riotredesign.features.navigation
 
 import android.app.Activity
+import android.content.Context
 import android.content.Intent
 import androidx.fragment.app.Fragment
 import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
@@ -27,29 +28,28 @@ import im.vector.riotredesign.features.roomdirectory.RoomDirectoryActivity
 import im.vector.riotredesign.features.roomdirectory.roompreview.RoomPreviewActivity
 import im.vector.riotredesign.features.settings.VectorSettingsActivity
 
-class DefaultNavigator(private val fraqment: Fragment) : Navigator {
+class DefaultNavigator : Navigator {
 
-    val activity: Activity = fraqment.requireActivity()
 
-    override fun openRoom(roomId: String) {
+    override fun openRoom(roomId: String, context: Context) {
         val args = RoomDetailArgs(roomId)
-        val intent = RoomDetailActivity.newIntent(activity, args)
-        activity.startActivity(intent)
+        val intent = RoomDetailActivity.newIntent(context, args)
+        context.startActivity(intent)
     }
 
-    override fun openRoomPreview(publicRoom: PublicRoom) {
-        val intent = RoomPreviewActivity.getIntent(activity, publicRoom)
-        activity.startActivity(intent)
+    override fun openRoomPreview(publicRoom: PublicRoom, context: Context) {
+        val intent = RoomPreviewActivity.getIntent(context, publicRoom)
+        context.startActivity(intent)
     }
 
-    override fun openRoomDirectory() {
-        val intent = Intent(activity, RoomDirectoryActivity::class.java)
-        activity.startActivity(intent)
+    override fun openRoomDirectory(context: Context) {
+        val intent = Intent(context, RoomDirectoryActivity::class.java)
+        context.startActivity(intent)
     }
 
-    override fun openSettings() {
-        val intent = VectorSettingsActivity.getIntent(activity, "TODO")
-        activity.startActivity(intent)
+    override fun openSettings(context: Context) {
+        val intent = VectorSettingsActivity.getIntent(context, "TODO")
+        context.startActivity(intent)
     }
 
     override fun openDebug() {
diff --git a/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt
index 518e8de818..0229596474 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt
@@ -16,17 +16,18 @@
 
 package im.vector.riotredesign.features.navigation
 
+import android.content.Context
 import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
 
 interface Navigator {
 
-    fun openRoom(roomId: String)
+    fun openRoom(roomId: String, context: Context)
 
-    fun openRoomPreview(publicRoom: PublicRoom)
+    fun openRoomPreview(publicRoom: PublicRoom, context: Context)
 
-    fun openRoomDirectory()
+    fun openRoomDirectory(context: Context)
 
-    fun openSettings()
+    fun openSettings(context: Context)
 
     fun openDebug()
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt
index aae438b7d6..5deab028f2 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/PublicRoomsFragment.kt
@@ -124,12 +124,12 @@ class PublicRoomsFragment : VectorBaseFragment(), PublicRoomsController.Callback
 
         when (joinState) {
             JoinState.JOINED        -> {
-                navigator.openRoom(publicRoom.roomId)
+                navigator.openRoom(publicRoom.roomId, requireActivity())
             }
             JoinState.NOT_JOINED,
             JoinState.JOINING_ERROR -> {
                 // ROOM PREVIEW
-                navigator.openRoomPreview(publicRoom)
+                navigator.openRoomPreview(publicRoom, requireActivity())
             }
             else                    -> {
                 Snackbar.make(publicRoomsCoordinator, getString(R.string.please_wait), Snackbar.LENGTH_SHORT)
diff --git a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt
index 074995557a..3f389a4609 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/roomdirectory/roompreview/RoomPreviewNoPreviewFragment.kt
@@ -108,7 +108,7 @@ class RoomPreviewNoPreviewFragment : VectorBaseFragment() {
             // Quit this screen
             requireActivity().finish()
             // Open room
-            navigator.openRoom(roomPreviewData.roomId)
+            navigator.openRoom(roomPreviewData.roomId, requireActivity())
         }
     }
 }
\ No newline at end of file

From 3f1bf00fdd3c5a746088c1983d8cd1e68aa4f60f Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Thu, 6 Jun 2019 12:59:26 +0200
Subject: [PATCH 10/18] Fix / use emoji Compat font for view reaction screen

---
 .../timeline/action/ReactionInfoSimpleItem.kt       | 13 ++++++++++---
 .../timeline/action/ViewReactionBottomSheet.kt      | 11 +++++++----
 .../timeline/action/ViewReactionsEpoxyController.kt |  8 ++++++--
 3 files changed, 23 insertions(+), 9 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt
index 7c7e253167..647af4b0f8 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ReactionInfoSimpleItem.kt
@@ -1,5 +1,6 @@
 package im.vector.riotredesign.features.home.room.detail.timeline.action
 
+import android.graphics.Typeface
 import android.widget.TextView
 import androidx.core.view.isVisible
 import com.airbnb.epoxy.EpoxyAttribute
@@ -8,7 +9,9 @@ import com.airbnb.epoxy.EpoxyModelWithHolder
 import im.vector.riotredesign.R
 import im.vector.riotredesign.core.epoxy.VectorEpoxyHolder
 
-
+/**
+ * Item displaying an emoji reaction (single line with emoji, author, time)
+ */
 @EpoxyModelClass(layout = R.layout.item_simple_reaction_info)
 abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleItem.Holder>() {
 
@@ -19,8 +22,12 @@ abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleI
     @EpoxyAttribute
     var timeStamp: CharSequence? = null
 
+    @EpoxyAttribute
+    var emojiTypeFace: Typeface? = null
+
     override fun bind(holder: Holder) {
-        holder.titleView.text = reactionKey
+        holder.emojiReactionView.text = reactionKey
+        holder.emojiReactionView.typeface = emojiTypeFace ?: Typeface.DEFAULT
         holder.displayNameView.text = authorDisplayName
         timeStamp?.let {
             holder.timeStampView.text = it
@@ -31,7 +38,7 @@ abstract class ReactionInfoSimpleItem : EpoxyModelWithHolder<ReactionInfoSimpleI
     }
 
     class Holder : VectorEpoxyHolder() {
-        val titleView by bind<TextView>(R.id.itemSimpleReactionInfoKey)
+        val emojiReactionView by bind<TextView>(R.id.itemSimpleReactionInfoKey)
         val displayNameView by bind<TextView>(R.id.itemSimpleReactionInfoMemberName)
         val timeStampView by bind<TextView>(R.id.itemSimpleReactionInfoTime)
     }
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt
index a53dc3bd78..308c68bf9d 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionBottomSheet.kt
@@ -11,24 +11,27 @@ import butterknife.BindView
 import butterknife.ButterKnife
 import com.airbnb.epoxy.EpoxyRecyclerView
 import com.airbnb.mvrx.MvRx
-import com.airbnb.mvrx.args
 import com.airbnb.mvrx.fragmentViewModel
 import com.airbnb.mvrx.withState
+import im.vector.riotredesign.EmojiCompatFontProvider
 import im.vector.riotredesign.R
 import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
 import kotlinx.android.synthetic.main.bottom_sheet_display_reactions.*
+import org.koin.android.ext.android.inject
 
-
+/**
+ * Bottom sheet displaying list of reactions for a given event ordered by timestamp
+ */
 class ViewReactionBottomSheet : BaseMvRxBottomSheetDialog() {
 
     private val viewModel: ViewReactionViewModel by fragmentViewModel(ViewReactionViewModel::class)
 
-    private val eventArgs: TimelineEventFragmentArgs by args()
+    private val emojiCompatFontProvider by inject<EmojiCompatFontProvider>()
 
     @BindView(R.id.bottom_sheet_display_reactions_list)
     lateinit var epoxyRecyclerView: EpoxyRecyclerView
 
-    private val epoxyController by lazy { ViewReactionsEpoxyController() }
+    private val epoxyController by lazy { ViewReactionsEpoxyController(emojiCompatFontProvider.typeface) }
 
     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
         val view = inflater.inflate(R.layout.bottom_sheet_display_reactions, container, false)
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt
index a3d146a24f..461fec2b13 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/ViewReactionsEpoxyController.kt
@@ -1,15 +1,19 @@
 package im.vector.riotredesign.features.home.room.detail.timeline.action
 
+import android.graphics.Typeface
 import com.airbnb.epoxy.TypedEpoxyController
 
-
-class ViewReactionsEpoxyController : TypedEpoxyController<DisplayReactionsViewState>() {
+/**
+ * Epoxy controller for reaction event list
+ */
+class ViewReactionsEpoxyController(private val emojiCompatTypeface: Typeface?) : TypedEpoxyController<DisplayReactionsViewState>() {
 
     override fun buildModels(state: DisplayReactionsViewState) {
         val map = state.mapReactionKeyToMemberList() ?: return
         map.forEach {
             reactionInfoSimpleItem {
                 id(it.eventId)
+                emojiTypeFace(emojiCompatTypeface)
                 timeStamp(it.timestamp)
                 reactionKey(it.reactionKey)
                 authorDisplayName(it.authorName ?: it.authorId)

From 740900394916a91ccd112a0366dec6953b102c0c Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Fri, 7 Jun 2019 10:01:42 +0200
Subject: [PATCH 11/18] Fix / Bug aggregation on initial sync

fix / All messages were not processed due to a test exiting the for loop
+ started adding context menu for non room messages
---
 matrix-sdk-android/build.gradle               |   4 +-
 .../internal/database/mapper/EventMapper.kt   |  13 +-
 .../room/EventRelationsAggregationTask.kt     | 198 ++++++++++--------
 .../room/EventRelationsAggregationUpdater.kt  |   6 +-
 .../android/internal/task/TaskExecutor.kt     |   7 +-
 vector/build.gradle                           |   6 +-
 .../home/room/detail/RoomDetailFragment.kt    |   4 +-
 .../home/room/detail/RoomDetailViewModel.kt   |   3 +-
 .../timeline/TimelineEventController.kt       |   9 +-
 .../timeline/action/MessageMenuViewModel.kt   |  22 +-
 .../timeline/factory/NoticeItemFactory.kt     |  19 +-
 .../timeline/factory/TimelineItemFactory.kt   |  35 +++-
 .../helper/TimelineDisplayableEvents.kt       |   9 +
 .../room/detail/timeline/item/NoticeItem.kt   |  29 ++-
 .../item_timeline_event_base_noinfo.xml       |   3 +-
 15 files changed, 233 insertions(+), 134 deletions(-)

diff --git a/matrix-sdk-android/build.gradle b/matrix-sdk-android/build.gradle
index 1a0c0f9123..3fda5a6372 100644
--- a/matrix-sdk-android/build.gradle
+++ b/matrix-sdk-android/build.gradle
@@ -48,7 +48,7 @@ android {
             buildConfigField "boolean", "LOG_PRIVATE_DATA", "false"
 
             // Set to BODY instead of NONE to enable logging
-            buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.BASIC"
+            buildConfigField "okhttp3.logging.HttpLoggingInterceptor.Level", "OKHTTP_LOGGING_LEVEL", "okhttp3.logging.HttpLoggingInterceptor.Level.NONE"
         }
 
         release {
@@ -91,7 +91,7 @@ dependencies {
     def moshi_version = '1.8.0'
     def lifecycle_version = '2.0.0'
     def coroutines_version = "1.0.1"
-    def markwon_version = '3.0.0-SNAPSHOT'
+    def markwon_version = '3.0.0'
 
     implementation fileTree(dir: 'libs', include: ['*.aar'])
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt
index 3c21437e78..34b29187d3 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/EventMapper.kt
@@ -16,6 +16,7 @@
 
 package im.vector.matrix.android.internal.database.mapper
 
+import com.squareup.moshi.JsonDataException
 import im.vector.matrix.android.api.session.events.model.Event
 import im.vector.matrix.android.api.session.events.model.UnsignedData
 import im.vector.matrix.android.internal.database.model.EventEntity
@@ -46,8 +47,16 @@ internal object EventMapper {
 
     fun map(eventEntity: EventEntity): Event {
         //TODO proxy the event to only parse unsigned data when accessed?
-        var ud = if (eventEntity.unsignedData.isNullOrBlank()) null
-        else MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(eventEntity.unsignedData)
+        val ud = if (eventEntity.unsignedData.isNullOrBlank()) {
+            null
+        } else {
+            try {
+                MoshiProvider.providesMoshi().adapter(UnsignedData::class.java).fromJson(eventEntity.unsignedData)
+            } catch (t: JsonDataException) {
+                null
+            }
+
+        }
         return Event(
                 type = eventEntity.type,
                 eventId = eventEntity.eventId,
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt
index e5fb5493bf..3b02494237 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationTask.kt
@@ -27,7 +27,6 @@ import im.vector.matrix.android.internal.database.model.*
 import im.vector.matrix.android.internal.database.query.create
 import im.vector.matrix.android.internal.database.query.where
 import im.vector.matrix.android.internal.task.Task
-import im.vector.matrix.android.internal.util.tryTransactionAsync
 import im.vector.matrix.android.internal.util.tryTransactionSync
 import io.realm.Realm
 import timber.log.Timber
@@ -49,60 +48,75 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
     private val SHOULD_HANDLE_SERVER_AGREGGATION = false
 
     override fun execute(params: EventRelationsAggregationTask.Params): Try<Unit> {
+        val events = params.events
+        val userId = params.userId
         return monarchy.tryTransactionSync { realm ->
-            update(realm, params.events, params.userId)
+            Timber.v(">>> DefaultEventRelationsAggregationTask[${params.hashCode()}] called with ${events.size} events")
+            update(realm, events, userId)
+            Timber.v("<<< DefaultEventRelationsAggregationTask[${params.hashCode()}] finished")
         }
     }
 
     private fun update(realm: Realm, events: List<Pair<Event, SendState>>, userId: String) {
         events.forEach { pair ->
-            val roomId = pair.first.roomId ?: return@forEach
-            val event = pair.first
-            val sendState = pair.second
-            val isLocalEcho = sendState == SendState.UNSENT
-            when (event.type) {
-                EventType.REACTION  -> {
-                    //we got a reaction!!
-                    Timber.v("###REACTION in room $roomId")
-                    handleReaction(event, roomId, realm, userId, isLocalEcho)
+            try { //Temporary catch, should be removed
+                val roomId = pair.first.roomId
+                if (roomId == null) {
+                    Timber.w("Event has no room id ${pair.first.eventId}")
+                    return@forEach
                 }
-                EventType.MESSAGE   -> {
-                    if (event.unsignedData?.relations?.annotations != null) {
-                        Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}")
-                        handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm)
-                    } else {
-                        val content: MessageContent? = event.content.toModel()
-                        if (content?.relatesTo?.type == RelationType.REPLACE) {
-                            Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
-                            //A replace!
-                            handleReplace(realm, event, content, roomId, isLocalEcho)
-                        }
+                val event = pair.first
+                val sendState = pair.second
+                val isLocalEcho = sendState == SendState.UNSENT
+                when (event.type) {
+                    EventType.REACTION  -> {
+                        //we got a reaction!!
+                        Timber.v("###REACTION in room $roomId , reaction eventID ${event.eventId}")
+                        handleReaction(event, roomId, realm, userId, isLocalEcho)
                     }
-
-                }
-                EventType.REDACTION -> {
-                    val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
-                            ?: return
-                    when (eventToPrune.type) {
-                        EventType.MESSAGE  -> {
-                            Timber.d("REDACTION for message ${eventToPrune.eventId}")
-                            val unsignedData = EventMapper.map(eventToPrune).unsignedData
-                                    ?: UnsignedData(null, null)
-
-                            //was this event a m.replace
-                            val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>()
-                            if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) {
-                                handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm)
+                    EventType.MESSAGE   -> {
+                        if (event.unsignedData?.relations?.annotations != null) {
+                            Timber.v("###REACTION Agreggation in room $roomId for event ${event.eventId}")
+                            handleInitialAggregatedRelations(event, roomId, event.unsignedData.relations.annotations, realm)
+                        } else {
+                            val content: MessageContent? = event.content.toModel()
+                            if (content?.relatesTo?.type == RelationType.REPLACE) {
+                                Timber.v("###REPLACE in room $roomId for event ${event.eventId}")
+                                //A replace!
+                                handleReplace(realm, event, content, roomId, isLocalEcho)
                             }
-
                         }
-                        EventType.REACTION -> {
-                            handleReactionRedact(eventToPrune, realm, userId)
+
+                    }
+                    EventType.REDACTION -> {
+                        val eventToPrune = event.redacts?.let { EventEntity.where(realm, eventId = it).findFirst() }
+                                ?: return@forEach
+                        when (eventToPrune.type) {
+                            EventType.MESSAGE  -> {
+                                Timber.d("REDACTION for message ${eventToPrune.eventId}")
+                                val unsignedData = EventMapper.map(eventToPrune).unsignedData
+                                        ?: UnsignedData(null, null)
+
+                                //was this event a m.replace
+                                val contentModel = ContentMapper.map(eventToPrune.content)?.toModel<MessageContent>()
+                                if (RelationType.REPLACE == contentModel?.relatesTo?.type && contentModel.relatesTo?.eventId != null) {
+                                    handleRedactionOfReplace(eventToPrune, contentModel.relatesTo!!.eventId!!, realm)
+                                }
+
+                            }
+                            EventType.REACTION -> {
+                                handleReactionRedact(eventToPrune, realm, userId)
+                            }
                         }
                     }
+                    else                -> Timber.v("UnHandled event ${event.eventId}")
                 }
+
+            } catch (t: Throwable) {
+                Timber.e(t, "## Should not happen ")
             }
         }
+
     }
 
     private fun handleReplace(realm: Realm, event: Event, content: MessageContent, roomId: String, isLocalEcho: Boolean) {
@@ -112,7 +126,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
         //ok, this is a replace
         var existing = EventAnnotationsSummaryEntity.where(realm, targetEventId).findFirst()
         if (existing == null) {
-            Timber.v("###REPLACE creating no relation summary for ${targetEventId}")
+            Timber.v("###REPLACE creating new relation summary for ${targetEventId}")
             existing = EventAnnotationsSummaryEntity.create(realm, targetEventId)
             existing.roomId = roomId
         }
@@ -120,7 +134,7 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
         //we have it
         val existingSummary = existing.editSummary
         if (existingSummary == null) {
-            Timber.v("###REPLACE no edit summary for ${targetEventId}, creating one (localEcho:$isLocalEcho)")
+            Timber.v("###REPLACE new edit summary for ${targetEventId}, creating one (localEcho:$isLocalEcho)")
             //create the edit summary
             val editSummary = realm.createObject(EditAggregatedSummaryEntity::class.java)
             editSummary.lastEditTs = event.originServerTs ?: System.currentTimeMillis()
@@ -181,62 +195,70 @@ internal class DefaultEventRelationsAggregationTask(private val monarchy: Monarc
     }
 
     private fun handleReaction(event: Event, roomId: String, realm: Realm, userId: String, isLocalEcho: Boolean) {
-        event.content.toModel<ReactionContent>()?.let { content ->
-            //rel_type must be m.annotation
-            if (RelationType.ANNOTATION == content.relatesTo?.type) {
-                val reaction = content.relatesTo.key
-                val eventId = content.relatesTo.eventId
-                val eventSummary = EventAnnotationsSummaryEntity.where(realm, eventId).findFirst()
-                        ?: EventAnnotationsSummaryEntity.create(realm, eventId).apply { this.roomId = roomId }
+        val content = event.content.toModel<ReactionContent>()
+        if (content == null) {
+            Timber.e("Malformed reaction content ${event.content}")
+            return
+        }
+        //rel_type must be m.annotation
+        if (RelationType.ANNOTATION == content.relatesTo?.type) {
+            val reaction = content.relatesTo.key
+            val relatedEventID = content.relatesTo.eventId
+            val reactionEventId = event.eventId
+            Timber.v("Reaction $reactionEventId relates to $relatedEventID")
+            val eventSummary = EventAnnotationsSummaryEntity.where(realm, relatedEventID).findFirst()
+                    ?: EventAnnotationsSummaryEntity.create(realm, relatedEventID).apply { this.roomId = roomId }
 
-                var sum = eventSummary.reactionsSummary.find { it.key == reaction }
-                val txId = event.unsignedData?.transactionId
-                if (isLocalEcho && txId.isNullOrBlank()) {
-                    Timber.w("Received a local echo with no transaction ID")
-                }
-                if (sum == null) {
-                    sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
-                    sum.key = reaction
-                    sum.firstTimestamp = event.originServerTs ?: 0
-                    if (isLocalEcho) {
-                        Timber.v("Adding local echo reaction $reaction")
-                        sum.sourceLocalEcho.add(txId)
-                        sum.count = 1
-                    } else {
-                        Timber.v("Adding synced reaction $reaction")
-                        sum.count = 1
-                        sum.sourceEvents.add(event.eventId)
-                    }
-                    sum.addedByMe = sum.addedByMe || (userId == event.sender)
-                    eventSummary.reactionsSummary.add(sum)
+            var sum = eventSummary.reactionsSummary.find { it.key == reaction }
+            val txId = event.unsignedData?.transactionId
+            if (isLocalEcho && txId.isNullOrBlank()) {
+                Timber.w("Received a local echo with no transaction ID")
+            }
+            if (sum == null) {
+                sum = realm.createObject(ReactionAggregatedSummaryEntity::class.java)
+                sum.key = reaction
+                sum.firstTimestamp = event.originServerTs ?: 0
+                if (isLocalEcho) {
+                    Timber.v("Adding local echo reaction $reaction")
+                    sum.sourceLocalEcho.add(txId)
+                    sum.count = 1
                 } else {
-                    //is this a known event (is possible? pagination?)
-                    if (!sum.sourceEvents.contains(eventId)) {
+                    Timber.v("Adding synced reaction $reaction")
+                    sum.count = 1
+                    sum.sourceEvents.add(reactionEventId)
+                }
+                sum.addedByMe = sum.addedByMe || (userId == event.sender)
+                eventSummary.reactionsSummary.add(sum)
+            } else {
+                //is this a known event (is possible? pagination?)
+                if (!sum.sourceEvents.contains(reactionEventId)) {
 
-                        //check if it's not the sync of a local echo
-                        if (!isLocalEcho && sum.sourceLocalEcho.contains(txId)) {
-                            //ok it has already been counted, just sync the list, do not touch count
-                            Timber.v("Ignoring synced of local echo for reaction $reaction")
-                            sum.sourceLocalEcho.remove(txId)
-                            sum.sourceEvents.add(event.eventId)
+                    //check if it's not the sync of a local echo
+                    if (!isLocalEcho && sum.sourceLocalEcho.contains(txId)) {
+                        //ok it has already been counted, just sync the list, do not touch count
+                        Timber.v("Ignoring synced of local echo for reaction $reaction")
+                        sum.sourceLocalEcho.remove(txId)
+                        sum.sourceEvents.add(reactionEventId)
+                    } else {
+                        sum.count += 1
+                        if (isLocalEcho) {
+                            Timber.v("Adding local echo reaction $reaction")
+                            sum.sourceLocalEcho.add(txId)
                         } else {
-                            sum.count += 1
-                            if (isLocalEcho) {
-                                Timber.v("Adding local echo reaction $reaction")
-                                sum.sourceLocalEcho.add(txId)
-                            } else {
-                                Timber.v("Adding synced reaction $reaction")
-                                sum.sourceEvents.add(event.eventId)
-                            }
-
-                            sum.addedByMe = sum.addedByMe || (userId == event.sender)
+                            Timber.v("Adding synced reaction $reaction")
+                            sum.sourceEvents.add(reactionEventId)
                         }
 
+                        sum.addedByMe = sum.addedByMe || (userId == event.sender)
                     }
-                }
 
+                }
             }
+
+        } else {
+            Timber.e("Unknwon relation type ${content.relatesTo?.type} for event ${event.eventId}")
         }
+
     }
 
     /**
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt
index 0b29064c87..a04ed2160a 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/EventRelationsAggregationUpdater.kt
@@ -46,11 +46,11 @@ internal class EventRelationsAggregationUpdater(monarchy: Monarchy,
 
     override fun processChanges(inserted: List<EventEntity>, updated: List<EventEntity>, deleted: List<EventEntity>) {
         Timber.v("EventRelationsAggregationUpdater called with ${inserted.size} insertions")
-        val inserted = inserted
-                .mapNotNull { it.asDomain() to it.sendState }
+        val domainInserted = inserted
+                .map { it.asDomain() to it.sendState }
 
         val params = EventRelationsAggregationTask.Params(
-                inserted,
+                domainInserted,
                 credentials.userId
         )
 
diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt
index a685581764..caf75bd7b2 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/task/TaskExecutor.kt
@@ -38,7 +38,12 @@ internal class TaskExecutor(private val coroutineDispatchers: MatrixCoroutineDis
                     task.execute(task.params)
                 }
             }
-            resultOrFailure.fold({ task.callback.onFailure(it) }, { task.callback.onSuccess(it) })
+            resultOrFailure.fold({
+                Timber.d(it, "Task failed")
+                task.callback.onFailure(it)
+            }, {
+                task.callback.onSuccess(it)
+            })
         }
         return CancelableCoroutine(job)
     }
diff --git a/vector/build.gradle b/vector/build.gradle
index 4cec152fde..263df78e27 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -84,13 +84,15 @@ android {
         debug {
             resValue "bool", "debug_mode", "true"
             buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
-
+            buildConfigField "boolean", "SHOW_HIDDEN_TIMELINE_EVENTS", "false"
+            
             signingConfig signingConfigs.debug
         }
 
         release {
             resValue "bool", "debug_mode", "false"
             buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
+            buildConfigField "boolean", "SHOW_HIDDEN_TIMELINE_EVENTS", "false"
 
             minifyEnabled false
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
@@ -132,7 +134,7 @@ dependencies {
     def epoxy_version = "3.3.0"
     def arrow_version = "0.8.2"
     def coroutines_version = "1.0.1"
-    def markwon_version = '3.0.0-SNAPSHOT'
+    def markwon_version = '3.0.0'
     def big_image_viewer_version = '1.5.6'
     def glide_version = '4.9.0'
     def moshi_version = '1.8.0'
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
index 1da6ed9d13..552843258e 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailFragment.kt
@@ -563,11 +563,11 @@ class RoomDetailFragment :
         vectorBaseActivity.notImplemented()
     }
 
-    override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View) {
+    override fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View) {
 
     }
 
-    override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean {
+    override fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean {
         view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)
         val roomId = roomDetailArgs.roomId
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
index 351129a2f1..346d21c35f 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
@@ -55,7 +55,8 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
     private val roomId = initialState.roomId
     private val eventId = initialState.eventId
     private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
-    private val timeline = room.createTimeline(eventId, TimelineDisplayableEvents.DISPLAYABLE_TYPES)
+    private val timeline = room.createTimeline(eventId,
+            if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES else TimelineDisplayableEvents.DISPLAYABLE_TYPES)
 
     companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
index 2861ae6a70..2a436a192c 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/TimelineEventController.kt
@@ -46,15 +46,13 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
                               private val backgroundHandler: Handler = TimelineAsyncHelper.getBackgroundHandler()
 ) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener {
 
-    interface Callback : ReactionPillCallback, AvatarCallback {
+    interface Callback : ReactionPillCallback, AvatarCallback, BaseCallback {
         fun onEventVisible(event: TimelineEvent)
         fun onUrlClicked(url: String)
         fun onImageMessageClicked(messageImageContent: MessageImageContent, mediaData: ImageContentRenderer.Data, view: View)
         fun onVideoMessageClicked(messageVideoContent: MessageVideoContent, mediaData: VideoContentRenderer.Data, view: View)
         fun onFileMessageClicked(messageFileContent: MessageFileContent)
         fun onAudioMessageClicked(messageAudioContent: MessageAudioContent)
-        fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View)
-        fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent, view: View): Boolean
         fun onEditedDecorationClicked(informationData: MessageInformationData, editAggregatedSummary: EditAggregatedSummary?)
     }
 
@@ -63,6 +61,11 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
         fun onLongClickOnReactionPill(informationData: MessageInformationData, reaction: String)
     }
 
+    interface BaseCallback {
+        fun onEventCellClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View)
+        fun onEventLongClicked(informationData: MessageInformationData, messageContent: MessageContent?, view: View): Boolean
+    }
+
     interface AvatarCallback {
         fun onAvatarClicked(informationData: MessageInformationData)
         fun onMemberNameClicked(informationData: MessageInformationData)
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
index ca422f720c..6d99d41a2b 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
@@ -34,7 +34,7 @@ import org.koin.android.ext.android.get
 
 data class SimpleAction(val uid: String, val titleRes: Int, val iconResId: Int?, val data: Any? = null)
 
-data class MessageMenuState(val actions: List<SimpleAction>) : MvRxState
+data class MessageMenuState(val actions: List<SimpleAction> = emptyList()) : MvRxState
 
 /**
  * Manages list actions for a given message (copy / paste / forward...)
@@ -50,9 +50,9 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
             val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
                     ?: return null
 
-            val messageContent: MessageContent = event.annotations?.editSummary?.aggregatedContent?.toModel()
-                    ?: event.root.content.toModel() ?: return null
-            val type = messageContent.type
+            val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
+                    ?: event.root.content.toModel()
+            val type = messageContent?.type
 
             if (event.sendState == SendState.UNSENT) {
                 //Resend and Delete
@@ -76,7 +76,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                 this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, event.root.eventId))
                 if (canCopy(type)) {
                     //TODO copy images? html? see ClipBoard
-                    this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent.body))
+                    this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent!!.body))
                 }
 
                 if (canReply(event, messageContent)) {
@@ -134,10 +134,10 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
             return MessageMenuState(actions)
         }
 
-        private fun canReply(event: TimelineEvent, messageContent: MessageContent): Boolean {
+        private fun canReply(event: TimelineEvent, messageContent: MessageContent?): Boolean {
             //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
             if (event.root.type != EventType.MESSAGE) return false
-            return when (messageContent.type) {
+            return when (messageContent?.type) {
                 MessageType.MSGTYPE_TEXT,
                 MessageType.MSGTYPE_NOTICE,
                 MessageType.MSGTYPE_EMOTE,
@@ -149,10 +149,10 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
             }
         }
 
-        private fun canQuote(event: TimelineEvent, messageContent: MessageContent): Boolean {
+        private fun canQuote(event: TimelineEvent, messageContent: MessageContent?): Boolean {
             //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
             if (event.root.type != EventType.MESSAGE) return false
-            return when (messageContent.type) {
+            return when (messageContent?.type) {
                 MessageType.MSGTYPE_TEXT,
                 MessageType.MSGTYPE_NOTICE,
                 MessageType.MSGTYPE_EMOTE,
@@ -190,7 +190,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
         }
 
 
-        private fun canCopy(type: String): Boolean {
+        private fun canCopy(type: String?): Boolean {
             return when (type) {
                 MessageType.MSGTYPE_TEXT,
                 MessageType.MSGTYPE_NOTICE,
@@ -204,7 +204,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
         }
 
 
-        private fun canShare(type: String): Boolean {
+        private fun canShare(type: String?): Boolean {
             return when (type) {
                 MessageType.MSGTYPE_IMAGE,
                 MessageType.MSGTYPE_AUDIO,
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt
index fc756c9c12..dabebd2807 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/NoticeItemFactory.kt
@@ -17,23 +17,32 @@
 package im.vector.riotredesign.features.home.room.detail.timeline.factory
 
 import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
+import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
 import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
 import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar
 import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
+import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
 import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
 import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
 
 class NoticeItemFactory(private val eventFormatter: NoticeEventFormatter) {
 
-    fun create(event: TimelineEvent): NoticeItem? {
+    fun create(event: TimelineEvent,
+               callback: TimelineEventController.Callback?): NoticeItem? {
         val formattedText = eventFormatter.format(event) ?: return null
-        val senderName = event.senderName()
-        val senderAvatar = event.senderAvatar()
+        val informationData = MessageInformationData(
+                eventId = event.root.eventId ?: "?",
+                senderId = event.root.sender ?: "",
+                sendState = event.sendState,
+                avatarUrl = event.senderAvatar(),
+                memberName = event.senderName(),
+                showInformation = false
+        )
 
         return NoticeItem_()
                 .noticeText(formattedText)
-                .avatarUrl(senderAvatar)
-                .memberName(senderName)
+                .informationData(informationData)
+                .baseCallback(callback)
     }
 
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
index 022b7b0b10..c67ce71873 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/factory/TimelineItemFactory.kt
@@ -17,10 +17,16 @@
 package im.vector.riotredesign.features.home.room.detail.timeline.factory
 
 import im.vector.matrix.android.api.session.events.model.EventType
+import im.vector.matrix.android.api.session.events.model.toModel
+import im.vector.matrix.android.api.session.room.model.message.MessageContent
+import im.vector.matrix.android.api.session.room.model.message.MessageDefaultContent
 import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
 import im.vector.riotredesign.core.epoxy.EmptyItem_
 import im.vector.riotredesign.core.epoxy.VectorEpoxyModel
 import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
+import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDisplayableEvents
+import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
+import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageTextItem_
 import timber.log.Timber
 
 class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
@@ -43,7 +49,7 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
                 EventType.STATE_HISTORY_VISIBILITY,
                 EventType.CALL_INVITE,
                 EventType.CALL_HANGUP,
-                EventType.CALL_ANSWER       -> noticeItemFactory.create(event)
+                EventType.CALL_ANSWER       -> noticeItemFactory.create(event, callback)
 
                 // Unhandled event types (yet)
                 EventType.ENCRYPTED,
@@ -51,9 +57,32 @@ class TimelineItemFactory(private val messageItemFactory: MessageItemFactory,
                 EventType.STATE_ROOM_THIRD_PARTY_INVITE,
                 EventType.STICKER,
                 EventType.STATE_ROOM_CREATE -> defaultItemFactory.create(event)
+
                 else                        -> {
-                    Timber.w("Ignored event (type: ${event.root.type}")
-                    null
+                    //These are just for debug to display hidden event, they should be filtered out in normal mode
+                    if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) {
+                        val informationData = MessageInformationData(eventId = event.root.eventId
+                                ?: "?",
+                                senderId = event.root.sender ?: "",
+                                sendState = event.sendState,
+                                time = "",
+                                avatarUrl = null,
+                                memberName = "",
+                                showInformation = false
+                        )
+                        val messageContent = event.root.content.toModel<MessageContent>()
+                                ?: MessageDefaultContent("", "", null, null)
+                        MessageTextItem_()
+                                .informationData(informationData)
+                                .message("{ \"type\": ${event.root.type} }")
+                                .longClickListener { view ->
+                                    return@longClickListener callback?.onEventLongClicked(informationData, messageContent, view)
+                                            ?: false
+                                }
+                    } else {
+                        Timber.w("Ignored event (type: ${event.root.type}")
+                        null
+                    }
                 }
             }
         } catch (e: Exception) {
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
index 657d9e0367..711b870ee2 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/helper/TimelineDisplayableEvents.kt
@@ -22,10 +22,14 @@ import im.vector.matrix.android.api.session.events.model.toModel
 import im.vector.matrix.android.api.session.room.model.RoomMember
 import im.vector.matrix.android.api.session.room.model.message.MessageContent
 import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
+import im.vector.riotredesign.BuildConfig
 import im.vector.riotredesign.core.extensions.localDateTime
 
 object TimelineDisplayableEvents {
 
+    //Debug helper, to show invisible items in time line (reaction, redacts)
+    val DEBUG_HIDDEN_EVENT = BuildConfig.SHOW_HIDDEN_TIMELINE_EVENTS
+
     val DISPLAYABLE_TYPES = listOf(
             EventType.MESSAGE,
             EventType.STATE_ROOM_NAME,
@@ -41,6 +45,11 @@ object TimelineDisplayableEvents {
             EventType.STICKER,
             EventType.STATE_ROOM_CREATE
     )
+
+    val DEBUG_DISPLAYABLE_TYPES = DISPLAYABLE_TYPES + listOf(
+            EventType.REDACTION,
+            EventType.REACTION
+    )
 }
 
 fun TimelineEvent.isDisplayable(): Boolean {
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt
index dcb0bdf4ad..914cb23252 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt
@@ -23,27 +23,37 @@ import com.airbnb.epoxy.EpoxyAttribute
 import com.airbnb.epoxy.EpoxyModelClass
 import im.vector.riotredesign.R
 import im.vector.riotredesign.features.home.AvatarRenderer
+import im.vector.riotredesign.features.home.room.detail.timeline.TimelineEventController
 
 @EpoxyModelClass(layout = R.layout.item_timeline_event_base_noinfo)
 abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
 
     @EpoxyAttribute
     var noticeText: CharSequence? = null
-    @EpoxyAttribute
-    var avatarUrl: String? = null
-    @EpoxyAttribute
-    var userId: String = ""
-    @EpoxyAttribute
-    var memberName: CharSequence? = null
-
 
     @EpoxyAttribute
-    var longClickListener: View.OnLongClickListener? = null
+    lateinit var informationData: MessageInformationData
+
+    @EpoxyAttribute
+    var baseCallback: TimelineEventController.BaseCallback? = null
+
+
+    private var longClickListener = View.OnLongClickListener {
+        baseCallback?.onEventLongClicked(informationData, null, it)
+        baseCallback != null
+    }
+
 
     override fun bind(holder: Holder) {
         super.bind(holder)
         holder.noticeTextView.text = noticeText
-        AvatarRenderer.render(avatarUrl, userId, memberName?.toString(), holder.avatarImageView)
+        AvatarRenderer.render(
+                informationData.avatarUrl,
+                informationData.senderId,
+                informationData.memberName?.toString()
+                        ?: informationData.senderId,
+                holder.avatarImageView
+        )
         holder.view.setOnLongClickListener(longClickListener)
     }
 
@@ -51,7 +61,6 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
 
     class Holder : BaseHolder() {
         override fun getStubId(): Int = STUB_ID
-
         val avatarImageView by bind<ImageView>(R.id.itemNoticeAvatarView)
         val noticeTextView by bind<TextView>(R.id.itemNoticeTextView)
     }
diff --git a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml
index 07b43f7bc1..8163b3acdd 100644
--- a/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml
+++ b/vector/src/main/res/layout/item_timeline_event_base_noinfo.xml
@@ -4,6 +4,7 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:addStatesFromChildren="true"
+    android:background="?attr/selectableItemBackground"
     android:paddingLeft="8dp"
     android:paddingRight="8dp">
 
@@ -31,9 +32,9 @@
     <ViewStub
         android:id="@+id/messageContentBlankStub"
         style="@style/TimelineContentStubNoInfoLayoutParams"
-        android:layout="@layout/item_timeline_event_blank_stub"
         android:layout_width="0dp"
         android:layout_height="0dp"
+        android:layout="@layout/item_timeline_event_blank_stub"
         tools:ignore="MissingConstraints" />
 
     <ViewStub

From 5cf9deb329bf50c383e13159c7d1ecc84c1512c8 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Fri, 7 Jun 2019 10:45:24 +0200
Subject: [PATCH 12/18] Menu action for non room messages

---
 .../action/MessageActionsBottomSheet.kt       | 31 ++++++++++++-------
 .../action/MessageActionsViewModel.kt         | 27 +++++++++-------
 .../timeline/action/MessageMenuViewModel.kt   | 11 +++++--
 .../timeline/action/QuickReactionViewModel.kt | 21 ++++++-------
 .../room/detail/timeline/item/NoticeItem.kt   |  1 -
 .../layout/bottom_sheet_message_actions.xml   | 15 ++++++---
 6 files changed, 64 insertions(+), 42 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
index fa94043ed4..7ea6a9b1b8 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
@@ -23,6 +23,7 @@ import android.view.ViewGroup
 import android.widget.FrameLayout
 import android.widget.ImageView
 import android.widget.TextView
+import androidx.core.view.isVisible
 import androidx.lifecycle.ViewModelProviders
 import butterknife.BindView
 import butterknife.ButterKnife
@@ -35,6 +36,7 @@ import im.vector.riotredesign.R
 import im.vector.riotredesign.core.glide.GlideApp
 import im.vector.riotredesign.features.home.AvatarRenderer
 import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
+import kotlinx.android.synthetic.main.bottom_sheet_message_actions.*
 
 /**
  * Bottom sheet fragment that shows a message preview with list of contextual actions
@@ -115,20 +117,27 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {
     }
 
     override fun invalidate() = withState(viewModel) {
-        senderNameTextView.text = it.senderName
-        messageBodyTextView.text = it.messageBody
-        messageTimestampText.text = it.ts
+        if (it.showPreview) {
+            bottom_sheet_message_preview.isVisible = true
+            senderNameTextView.text = it.senderName
+            messageBodyTextView.text = it.messageBody
+            messageTimestampText.text = it.ts
 
-        GlideApp.with(this).clear(senderAvatarImageView)
-        if (it.senderAvatarPath != null) {
-            GlideApp.with(this)
-                    .load(it.senderAvatarPath)
-                    .circleCrop()
-                    .placeholder(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName))
-                    .into(senderAvatarImageView)
+            GlideApp.with(this).clear(senderAvatarImageView)
+            if (it.senderAvatarPath != null) {
+                GlideApp.with(this)
+                        .load(it.senderAvatarPath)
+                        .circleCrop()
+                        .placeholder(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName))
+                        .into(senderAvatarImageView)
+            } else {
+                senderAvatarImageView.setImageDrawable(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName))
+            }
         } else {
-            senderAvatarImageView.setImageDrawable(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName))
+            bottom_sheet_message_preview.isVisible = false
         }
+        quickReactBottomDivider.isVisible = it.canReact
+        bottom_sheet_quick_reaction_container.isVisible = it.canReact
         return@withState
     }
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index a307afe35a..2a925894b3 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -19,6 +19,7 @@ import com.airbnb.mvrx.MvRxState
 import com.airbnb.mvrx.MvRxViewModelFactory
 import com.airbnb.mvrx.ViewModelContext
 import im.vector.matrix.android.api.session.Session
+import im.vector.matrix.android.api.session.events.model.EventType
 import im.vector.matrix.android.api.session.events.model.toModel
 import im.vector.matrix.android.api.session.room.model.message.MessageContent
 import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
@@ -34,10 +35,12 @@ import java.util.*
 
 
 data class MessageActionState(
-        val userId: String,
-        val senderName: String,
-        val messageBody: CharSequence,
-        val ts: String?,
+        val userId: String = "",
+        val senderName: String = "",
+        val messageBody: CharSequence? = null,
+        val ts: String? = null,
+        val showPreview: Boolean = false,
+        val canReact: Boolean = false,
         val senderAvatarPath: String? = null)
     : MvRxState
 
@@ -59,21 +62,21 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode
                 val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
                         ?: event.root.content.toModel()
                 val originTs = event.root.originServerTs
-                var body: CharSequence = messageContent?.body ?: ""
+                var body: CharSequence? = messageContent?.body
                 if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
                     val parser = Parser.builder().build()
                     val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
-                   // val renderer = HtmlRenderer.builder().build()
                     body = Markwon.builder(viewModelContext.activity)
                             .usePlugin(HtmlPlugin.create()).build().render(document)
-//                    body = renderer.render(document)
                 }
                 MessageActionState(
-                        event.root.sender ?: "",
-                        parcel.informationData.memberName.toString(),
-                        body,
-                        dateFormat.format(Date(originTs ?: 0)),
-                        currentSession.contentUrlResolver().resolveFullSize(parcel.informationData.avatarUrl)
+                        userId = event.root.sender ?: "",
+                        senderName = parcel.informationData.memberName.toString(),
+                        messageBody = body,
+                        ts = dateFormat.format(Date(originTs ?: 0)),
+                        showPreview = event.root.type == EventType.MESSAGE,
+                        canReact = event.root.type == EventType.MESSAGE,
+                        senderAvatarPath = currentSession.contentUrlResolver().resolveFullSize(parcel.informationData.avatarUrl)
                 )
             } else {
                 //can this happen?
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
index 6d99d41a2b..d2511eff7e 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
@@ -73,7 +73,9 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                 }
                 //TODO is downloading attachement?
 
-                this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, event.root.eventId))
+                if (canReact(event, messageContent)) {
+                    this.add(SimpleAction(ACTION_ADD_REACTION, R.string.message_add_reaction, R.drawable.ic_add_reaction, event.root.eventId))
+                }
                 if (canCopy(type)) {
                     //TODO copy images? html? see ClipBoard
                     this.add(SimpleAction(ACTION_COPY, R.string.copy, R.drawable.ic_copy, messageContent!!.body))
@@ -125,7 +127,7 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                 }
                 this.add(SimpleAction(ACTION_COPY_PERMALINK, R.string.permalink, R.drawable.ic_permalink, parcel.eventId))
 
-                if (currentSession.sessionParams.credentials.userId != event.root.sender) {
+                if (currentSession.sessionParams.credentials.userId != event.root.sender && event.root.type == EventType.MESSAGE) {
                     //not sent by me
                     this.add(SimpleAction(ACTION_FLAG, R.string.report_content, R.drawable.ic_flag, parcel.eventId))
                 }
@@ -149,6 +151,11 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
             }
         }
 
+        private fun canReact(event: TimelineEvent, messageContent: MessageContent?): Boolean {
+            //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
+            return event.root.type == EventType.MESSAGE
+        }
+
         private fun canQuote(event: TimelineEvent, messageContent: MessageContent?): Boolean {
             //Only event of type Event.EVENT_TYPE_MESSAGE are supported for the moment
             if (event.root.type != EventType.MESSAGE) return false
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt
index 89976248b8..5252e51bda 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/QuickReactionViewModel.kt
@@ -32,15 +32,14 @@ enum class TriggleState {
 }
 
 data class QuickReactionState(
-        val agreeTrigleState: TriggleState,
-        val likeTriggleState: TriggleState,
+        val agreeTrigleState: TriggleState = TriggleState.NONE,
+        val likeTriggleState: TriggleState = TriggleState.NONE,
         /** Pair of 'clickedOn' and current toggles state*/
         val selectionResult: Pair<String, List<String>>? = null,
-        val eventId: String) : MvRxState
+        val eventId: String = "") : MvRxState
 
 /**
  * Quick reaction view model
- * TODO: configure initial state from event
  */
 class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel<QuickReactionState>(initialState) {
 
@@ -88,15 +87,15 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel
     private fun getReactions(state: QuickReactionState, newState1: TriggleState?, newState2: TriggleState?): List<String> {
         return ArrayList<String>(4).apply {
             when (newState2 ?: state.likeTriggleState) {
-                TriggleState.FIRST -> add(likePositive)
+                TriggleState.FIRST  -> add(likePositive)
                 TriggleState.SECOND -> add(likeNegative)
-                else -> {
+                else                -> {
                 }
             }
             when (newState1 ?: state.agreeTrigleState) {
-                TriggleState.FIRST -> add(agreePositive)
+                TriggleState.FIRST  -> add(agreePositive)
                 TriggleState.SECOND -> add(agreeNegative)
-                else -> {
+                else                -> {
                 }
             }
         }
@@ -114,9 +113,9 @@ class QuickReactionViewModel(initialState: QuickReactionState) : VectorViewModel
             return when (reaction) {
                 agreePositive -> agreeNegative
                 agreeNegative -> agreePositive
-                likePositive -> likeNegative
-                likeNegative -> likePositive
-                else -> null
+                likePositive  -> likeNegative
+                likeNegative  -> likePositive
+                else          -> null
             }
         }
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt
index 914cb23252..d190875f20 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/item/NoticeItem.kt
@@ -37,7 +37,6 @@ abstract class NoticeItem : BaseEventItem<NoticeItem.Holder>() {
     @EpoxyAttribute
     var baseCallback: TimelineEventController.BaseCallback? = null
 
-
     private var longClickListener = View.OnLongClickListener {
         baseCallback?.onEventLongClicked(informationData, null, it)
         baseCallback != null
diff --git a/vector/src/main/res/layout/bottom_sheet_message_actions.xml b/vector/src/main/res/layout/bottom_sheet_message_actions.xml
index 0de471bdd1..d1cb8c9fb9 100644
--- a/vector/src/main/res/layout/bottom_sheet_message_actions.xml
+++ b/vector/src/main/res/layout/bottom_sheet_message_actions.xml
@@ -86,7 +86,8 @@
                 tools:text="Friday 8pm" />
         </androidx.constraintlayout.widget.ConstraintLayout>
 
-        <LinearLayout
+        <View
+            android:id="@+id/quickReactTopDivider"
             android:layout_width="match_parent"
             android:layout_height="1dp"
             android:background="?attr/vctr_list_divider_color" />
@@ -94,18 +95,22 @@
         <FrameLayout
             android:id="@+id/bottom_sheet_quick_reaction_container"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+            tools:background="@android:color/holo_green_light"
+            tools:layout_height="180dp" />
 
-        <LinearLayout
+        <View
+            android:id="@+id/quickReactBottomDivider"
             android:layout_width="match_parent"
             android:layout_height="1dp"
             android:background="?attr/vctr_list_divider_color" />
 
-
         <FrameLayout
             android:id="@+id/bottom_sheet_menu_container"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content" />
+            android:layout_height="wrap_content"
+            tools:background="@android:color/holo_blue_dark"
+            tools:layout_height="250dp" />
 
     </LinearLayout>
 </androidx.core.widget.NestedScrollView>

From 651d0472cd408d963cca18bd189108d16ced9c32 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Fri, 7 Jun 2019 11:18:14 +0200
Subject: [PATCH 13/18] Show preview for notice events in context menu + fix
 merge issues

---
 vector/build.gradle                           |  2 +-
 .../features/home/HomeDrawerFragment.kt       |  2 +-
 .../action/MessageActionsViewModel.kt         | 42 ++++++++++++++-----
 .../features/navigation/DefaultNavigator.kt   |  4 +-
 .../features/navigation/Navigator.kt          |  2 +-
 5 files changed, 36 insertions(+), 16 deletions(-)

diff --git a/vector/build.gradle b/vector/build.gradle
index 263df78e27..f411f064e9 100644
--- a/vector/build.gradle
+++ b/vector/build.gradle
@@ -85,7 +85,7 @@ android {
             resValue "bool", "debug_mode", "true"
             buildConfigField "boolean", "LOW_PRIVACY_LOG_ENABLE", "false"
             buildConfigField "boolean", "SHOW_HIDDEN_TIMELINE_EVENTS", "false"
-            
+
             signingConfig signingConfigs.debug
         }
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt
index 97baa539b9..6619588157 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/HomeDrawerFragment.kt
@@ -59,7 +59,7 @@ class HomeDrawerFragment : VectorBaseFragment() {
 
         // Debug menu
         homeDrawerHeaderDebugView.setOnClickListener {
-            navigator.openDebug()
+            navigator.openDebug(requireActivity())
         }
     }
 }
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index 2a925894b3..1c29314035 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -15,6 +15,7 @@
  */
 package im.vector.riotredesign.features.home.room.detail.timeline.action
 
+import com.airbnb.mvrx.FragmentViewModelContext
 import com.airbnb.mvrx.MvRxState
 import com.airbnb.mvrx.MvRxViewModelFactory
 import com.airbnb.mvrx.ViewModelContext
@@ -25,8 +26,10 @@ import im.vector.matrix.android.api.session.room.model.message.MessageContent
 import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
 import im.vector.matrix.android.api.session.room.model.message.MessageType
 import im.vector.riotredesign.core.platform.VectorViewModel
+import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
 import org.commonmark.parser.Parser
 import org.koin.android.ext.android.get
+import org.koin.core.parameter.parametersOf
 import ru.noties.markwon.Markwon
 import ru.noties.markwon.html.HtmlPlugin
 import timber.log.Timber
@@ -53,28 +56,45 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode
 
         override fun initialState(viewModelContext: ViewModelContext): MessageActionState? {
             val currentSession = viewModelContext.activity.get<Session>()
+            val fragment = (viewModelContext as? FragmentViewModelContext)?.fragment
+            val noticeFormatter = fragment?.get<NoticeEventFormatter>(parameters = { parametersOf(fragment) })
             val parcel = viewModelContext.args as TimelineEventFragmentArgs
 
             val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())
 
             val event = currentSession.getRoom(parcel.roomId)?.getTimeLineEvent(parcel.eventId)
+            var body: CharSequence? = null
+            val originTs = event?.root?.originServerTs
             return if (event != null) {
-                val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
-                        ?: event.root.content.toModel()
-                val originTs = event.root.originServerTs
-                var body: CharSequence? = messageContent?.body
-                if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
-                    val parser = Parser.builder().build()
-                    val document = parser.parse(messageContent.formattedBody ?: messageContent.body)
-                    body = Markwon.builder(viewModelContext.activity)
-                            .usePlugin(HtmlPlugin.create()).build().render(document)
+                when (event.root.type) {
+                    EventType.MESSAGE     -> {
+                        val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent?.toModel()
+                                ?: event.root.content.toModel()
+                        body = messageContent?.body
+                        if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) {
+                            val parser = Parser.builder().build()
+                            val document = parser.parse(messageContent.formattedBody
+                                    ?: messageContent.body)
+                            body = Markwon.builder(viewModelContext.activity)
+                                    .usePlugin(HtmlPlugin.create()).build().render(document)
+                        }
+                    }
+                    EventType.STATE_ROOM_NAME,
+                    EventType.STATE_ROOM_TOPIC,
+                    EventType.STATE_ROOM_MEMBER,
+                    EventType.STATE_HISTORY_VISIBILITY,
+                    EventType.CALL_INVITE,
+                    EventType.CALL_HANGUP,
+                    EventType.CALL_ANSWER -> {
+                        body = noticeFormatter?.format(event)
+                    }
                 }
                 MessageActionState(
                         userId = event.root.sender ?: "",
-                        senderName = parcel.informationData.memberName.toString(),
+                        senderName = parcel.informationData.memberName?.toString() ?: "",
                         messageBody = body,
                         ts = dateFormat.format(Date(originTs ?: 0)),
-                        showPreview = event.root.type == EventType.MESSAGE,
+                        showPreview = body != null,
                         canReact = event.root.type == EventType.MESSAGE,
                         senderAvatarPath = currentSession.contentUrlResolver().resolveFullSize(parcel.informationData.avatarUrl)
                 )
diff --git a/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt
index 00207950ba..44c1cfe366 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/navigation/DefaultNavigator.kt
@@ -52,7 +52,7 @@ class DefaultNavigator : Navigator {
         context.startActivity(intent)
     }
 
-    override fun openDebug() {
-        activity.startActivity(Intent(activity, DebugMenuActivity::class.java))
+    override fun openDebug(context: Context) {
+        context.startActivity(Intent(context, DebugMenuActivity::class.java))
     }
 }
\ No newline at end of file
diff --git a/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt
index 0229596474..8873227b00 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/navigation/Navigator.kt
@@ -29,6 +29,6 @@ interface Navigator {
 
     fun openSettings(context: Context)
 
-    fun openDebug()
+    fun openDebug(context: Context)
 
 }
\ No newline at end of file

From 438404b5ba7f0ac3a418d10e4536e8560a0be6d0 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Fri, 7 Jun 2019 13:55:32 +0200
Subject: [PATCH 14/18] code review cleaning

---
 .../home/room/detail/RoomDetailViewModel.kt         |  8 ++++++--
 .../timeline/action/MessageActionsBottomSheet.kt    | 13 +------------
 .../timeline/action/MessageActionsViewModel.kt      |  2 +-
 .../features/reactions/widget/ReactionButton.kt     |  4 ++--
 4 files changed, 10 insertions(+), 17 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
index 346d21c35f..476ff99b86 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/RoomDetailViewModel.kt
@@ -55,8 +55,12 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
     private val roomId = initialState.roomId
     private val eventId = initialState.eventId
     private val displayedEventsObservable = BehaviorRelay.create<RoomDetailActions.EventDisplayed>()
-    private val timeline = room.createTimeline(eventId,
-            if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES else TimelineDisplayableEvents.DISPLAYABLE_TYPES)
+    private val allowedTypes = if (TimelineDisplayableEvents.DEBUG_HIDDEN_EVENT) {
+        TimelineDisplayableEvents.DEBUG_DISPLAYABLE_TYPES
+    } else {
+        TimelineDisplayableEvents.DISPLAYABLE_TYPES
+    }
+    private val timeline = room.createTimeline(eventId, allowedTypes)
 
     companion object : MvRxViewModelFactory<RoomDetailViewModel, RoomDetailViewState> {
 
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
index 7ea6a9b1b8..9c9bbc2db3 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsBottomSheet.kt
@@ -33,7 +33,6 @@ import com.airbnb.mvrx.withState
 import com.google.android.material.bottomsheet.BottomSheetBehavior
 import com.google.android.material.bottomsheet.BottomSheetDialog
 import im.vector.riotredesign.R
-import im.vector.riotredesign.core.glide.GlideApp
 import im.vector.riotredesign.features.home.AvatarRenderer
 import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
 import kotlinx.android.synthetic.main.bottom_sheet_message_actions.*
@@ -122,17 +121,7 @@ class MessageActionsBottomSheet : BaseMvRxBottomSheetDialog() {
             senderNameTextView.text = it.senderName
             messageBodyTextView.text = it.messageBody
             messageTimestampText.text = it.ts
-
-            GlideApp.with(this).clear(senderAvatarImageView)
-            if (it.senderAvatarPath != null) {
-                GlideApp.with(this)
-                        .load(it.senderAvatarPath)
-                        .circleCrop()
-                        .placeholder(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName))
-                        .into(senderAvatarImageView)
-            } else {
-                senderAvatarImageView.setImageDrawable(AvatarRenderer.getPlaceholderDrawable(requireContext(), it.userId, it.senderName))
-            }
+            AvatarRenderer.render(it.senderAvatarPath, it.userId, it.senderName, senderAvatarImageView)
         } else {
             bottom_sheet_message_preview.isVisible = false
         }
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index 1c29314035..d0ddb854c2 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -96,7 +96,7 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode
                         ts = dateFormat.format(Date(originTs ?: 0)),
                         showPreview = body != null,
                         canReact = event.root.type == EventType.MESSAGE,
-                        senderAvatarPath = currentSession.contentUrlResolver().resolveFullSize(parcel.informationData.avatarUrl)
+                        senderAvatarPath = parcel.informationData.avatarUrl
                 )
             } else {
                 //can this happen?
diff --git a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt
index c0716cf28e..8250b66854 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/reactions/widget/ReactionButton.kt
@@ -25,7 +25,6 @@ import android.graphics.Typeface
 import android.graphics.drawable.Drawable
 import android.util.AttributeSet
 import android.view.LayoutInflater
-import android.view.MotionEvent
 import android.view.View
 import android.view.animation.AccelerateDecelerateInterpolator
 import android.view.animation.DecelerateInterpolator
@@ -44,7 +43,8 @@ import im.vector.riotredesign.core.utils.TextUtils
  * Displays a String reaction (emoji), with a count, and that can be selected or not (toggle)
  */
 class ReactionButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
-                                               defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener {
+                                               defStyleAttr: Int = 0)
+    : FrameLayout(context, attrs, defStyleAttr), View.OnClickListener, View.OnLongClickListener {
 
     companion object {
         private val DECCELERATE_INTERPOLATOR = DecelerateInterpolator()

From 5f34e58bd3c046db66138692a2c18067f99c6b90 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Fri, 7 Jun 2019 14:29:42 +0200
Subject: [PATCH 15/18] Fix / style on emoji picker appbar layout

---
 .../res/layout/activity_emoji_reaction_picker.xml    | 12 +++++-------
 vector/src/main/res/values/styles_riot.xml           |  4 ++++
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml
index 38694933b3..6611f2667e 100644
--- a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml
+++ b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml
@@ -15,25 +15,23 @@
         tools:layout="@layout/emoji_chooser_fragment" />
 
     <com.google.android.material.appbar.AppBarLayout
+        style="@style/VectorAppBarLayoutStyle"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
+        android:layout_height="wrap_content"
+        android:elevation="4dp">
 
         <androidx.appcompat.widget.Toolbar
             android:id="@+id/emojiPickerToolbar"
             android:layout_width="match_parent"
             android:layout_height="?attr/actionBarSize"
-            android:background="?attr/colorPrimary"
-            android:elevation="4dp"
             android:minHeight="0dp"
-            android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
+            tools:title="@string/reactions"
             app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways" />
 
         <com.google.android.material.tabs.TabLayout
             android:id="@+id/tabs"
             android:layout_width="match_parent"
-            android:layout_height="40dp"
-            android:background="?attr/colorPrimary"
-            android:elevation="4dp" />
+            android:layout_height="40dp" />
 
     </com.google.android.material.appbar.AppBarLayout>
 
diff --git a/vector/src/main/res/values/styles_riot.xml b/vector/src/main/res/values/styles_riot.xml
index fb536a604e..1cd18d5ee7 100644
--- a/vector/src/main/res/values/styles_riot.xml
+++ b/vector/src/main/res/values/styles_riot.xml
@@ -26,6 +26,10 @@
         <item name="android:fontFamily">"sans-serif"</item>
     </style>
 
+    <style name="VectorAppBarLayoutStyle" parent="Widget.Design.AppBarLayout">
+        <item name="android:background">?riotx_background</item>
+    </style>
+
     <!-- Alert Dialog: Button color are not colorAccent by default -->
     <style name="VectorAlertDialogStyleLight" parent="Theme.MaterialComponents.Light.Dialog.Alert">
         <item name="buttonBarButtonStyle">@style/VectorAlertDialogButtonStyle</item>

From c9240c2dce5c3d7781eea7d4e0e8520ee355675c Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Fri, 7 Jun 2019 15:49:41 +0200
Subject: [PATCH 16/18] Fix / disable context menu on not sent messages

---
 .../detail/timeline/action/MessageActionsViewModel.kt    | 2 +-
 .../room/detail/timeline/action/MessageMenuViewModel.kt  | 9 +++++----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
index d0ddb854c2..998ddf8195 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageActionsViewModel.kt
@@ -95,7 +95,7 @@ class MessageActionsViewModel(initialState: MessageActionState) : VectorViewMode
                         messageBody = body,
                         ts = dateFormat.format(Date(originTs ?: 0)),
                         showPreview = body != null,
-                        canReact = event.root.type == EventType.MESSAGE,
+                        canReact = event.root.type == EventType.MESSAGE && event.sendState.isSent(),
                         senderAvatarPath = parcel.informationData.avatarUrl
                 )
             } else {
diff --git a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
index d2511eff7e..64269ad98c 100644
--- a/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
+++ b/vector/src/main/java/im/vector/riotredesign/features/home/room/detail/timeline/action/MessageMenuViewModel.kt
@@ -54,13 +54,14 @@ class MessageMenuViewModel(initialState: MessageMenuState) : VectorViewModel<Mes
                     ?: event.root.content.toModel()
             val type = messageContent?.type
 
-            if (event.sendState == SendState.UNSENT) {
+            if (!event.sendState.isSent()) {
                 //Resend and Delete
                 return MessageMenuState(
+                        //TODO
                         listOf(
-                                SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_send, event.root.eventId),
-                                //TODO delete icon
-                                SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId)
+//                                SimpleAction(ACTION_RESEND, R.string.resend, R.drawable.ic_send, event.root.eventId),
+//                                //TODO delete icon
+//                                SimpleAction(ACTION_DELETE, R.string.delete, R.drawable.ic_delete, event.root.eventId)
                         )
                 )
             }

From 10251b906a24dde4c42902cf83e2e7b36f2e6ce8 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Fri, 7 Jun 2019 15:57:55 +0200
Subject: [PATCH 17/18] clean / format

---
 vector/src/main/res/layout/activity_emoji_reaction_picker.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml
index 6611f2667e..475ff54c3a 100644
--- a/vector/src/main/res/layout/activity_emoji_reaction_picker.xml
+++ b/vector/src/main/res/layout/activity_emoji_reaction_picker.xml
@@ -25,8 +25,8 @@
             android:layout_width="match_parent"
             android:layout_height="?attr/actionBarSize"
             android:minHeight="0dp"
-            tools:title="@string/reactions"
-            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways" />
+            app:layout_scrollFlags="scroll|exitUntilCollapsed|snap|enterAlways"
+            tools:title="@string/reactions" />
 
         <com.google.android.material.tabs.TabLayout
             android:id="@+id/tabs"

From a4a813708cb2cd87d63940ba0cca82c41d3571a4 Mon Sep 17 00:00:00 2001
From: Valere <valeref@matrix.org>
Date: Fri, 7 Jun 2019 16:39:45 +0200
Subject: [PATCH 18/18] Fix / send state always returning Unknown

---
 .../android/internal/database/model/EventEntity.kt | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt
index 38f4f4518c..5b3e869058 100644
--- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt
+++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/EventEntity.kt
@@ -19,12 +19,10 @@ package im.vector.matrix.android.internal.database.model
 import im.vector.matrix.android.api.session.room.send.SendState
 import io.realm.RealmObject
 import io.realm.RealmResults
-import io.realm.annotations.Ignore
 import io.realm.annotations.Index
 import io.realm.annotations.LinkingObjects
 import io.realm.annotations.PrimaryKey
 import java.util.*
-import kotlin.properties.Delegates
 
 internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUID().toString(),
                                 @Index var eventId: String = "",
@@ -51,10 +49,14 @@ internal open class EventEntity(@PrimaryKey var localId: String = UUID.randomUUI
 
     private var sendStateStr: String = SendState.UNKNOWN.name
 
-    @delegate:Ignore
-    var sendState: SendState by Delegates.observable(SendState.valueOf(sendStateStr)) { _, _, newValue ->
-        sendStateStr = newValue.name
-    }
+    var sendState: SendState
+        get() {
+            return SendState.valueOf(sendStateStr)
+        }
+        set(value) {
+            sendStateStr = value.name
+        }
+
 
     companion object