diff --git a/app/src/main/java/org/futo/circles/App.kt b/app/src/main/java/org/futo/circles/App.kt
index 3bad960c7dde5cf95b1671c82975230b9cfa899c..76ad05db221b18fd719f552acccae6901ff64470 100644
--- a/app/src/main/java/org/futo/circles/App.kt
+++ b/app/src/main/java/org/futo/circles/App.kt
@@ -14,6 +14,7 @@ import org.futo.circles.feature.notifications.FcmHelper
 import org.futo.circles.feature.notifications.GuardServiceStarter
 import org.futo.circles.feature.notifications.NotificationUtils
 import org.futo.circles.feature.notifications.PushRuleTriggerListener
+import org.futo.circles.feature.timeline.post.emoji.RecentEmojisProvider
 import org.matrix.android.sdk.api.session.Session
 import timber.log.Timber
 import javax.inject.Inject
@@ -63,6 +64,7 @@ class App : Application() {
         notificationUtils.createNotificationChannels()
         setupLifecycleObserver()
         if (BuildConfig.DEBUG) Timber.plant(Timber.DebugTree())
+        RecentEmojisProvider.saveDefaultRecentEmojis(applicationContext)
     }
 
     private fun setupLifecycleObserver() {
diff --git a/app/src/main/java/org/futo/circles/feature/circles/CirclesFragment.kt b/app/src/main/java/org/futo/circles/feature/circles/CirclesFragment.kt
index 928d6c14e60909f5c02171b2aec029976721a727..4b223a6a6b7017e23fb9e71cdb644c30b6fbfda6 100644
--- a/app/src/main/java/org/futo/circles/feature/circles/CirclesFragment.kt
+++ b/app/src/main/java/org/futo/circles/feature/circles/CirclesFragment.kt
@@ -9,7 +9,6 @@ import androidx.recyclerview.widget.DividerItemDecoration
 import by.kirich1409.viewbindingdelegate.viewBinding
 import dagger.hilt.android.AndroidEntryPoint
 import org.futo.circles.core.databinding.FragmentRoomsBinding
-import org.futo.circles.core.extensions.bindToFab
 import org.futo.circles.core.extensions.navigateSafe
 import org.futo.circles.core.extensions.observeData
 import org.futo.circles.core.extensions.observeResponse
diff --git a/app/src/main/java/org/futo/circles/feature/groups/GroupsFragment.kt b/app/src/main/java/org/futo/circles/feature/groups/GroupsFragment.kt
index 29729010dfcd54e72b99d9f884bc99699659e8ce..42994247a7bd66215bede370dd6e53da73e5cbb5 100644
--- a/app/src/main/java/org/futo/circles/feature/groups/GroupsFragment.kt
+++ b/app/src/main/java/org/futo/circles/feature/groups/GroupsFragment.kt
@@ -9,7 +9,6 @@ import androidx.recyclerview.widget.DividerItemDecoration
 import by.kirich1409.viewbindingdelegate.viewBinding
 import dagger.hilt.android.AndroidEntryPoint
 import org.futo.circles.core.databinding.FragmentRoomsBinding
-import org.futo.circles.core.extensions.bindToFab
 import org.futo.circles.core.extensions.navigateSafe
 import org.futo.circles.core.extensions.observeData
 import org.futo.circles.core.extensions.observeResponse
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt b/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt
index 8c0856a46079efb1e2dc663d8a8ada7082d3a9b2..1f29495cca6b999a7a21fbca42626bec78eb2408 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt
+++ b/app/src/main/java/org/futo/circles/feature/timeline/TimelineDialogFragment.kt
@@ -151,7 +151,7 @@ class TimelineDialogFragment : BaseFullscreenDialogFragment(DialogFragmentTimeli
         binding.rvTimeline.apply {
             adapter = listAdapter
             addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL))
-            MarkAsReadBuffer(this) { viewModel.markEventAsRead(it) }
+            MarkAsReadBuffer(this.getRecyclerView()) { viewModel.markEventAsRead(it) }
         }
         binding.lCreatePost.setUp(object : CreatePostViewListener {
             override fun onCreatePoll() {
@@ -161,7 +161,7 @@ class TimelineDialogFragment : BaseFullscreenDialogFragment(DialogFragmentTimeli
             override fun onCreatePost() {
                 navigator.navigateToCreatePost(timelineId, args.threadEventId)
             }
-        }, binding.rvTimeline, isThread)
+        }, binding.rvTimeline.getRecyclerView(), isThread)
     }
 
     private fun setupObservers() {
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/EmojiBottomSheet.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/EmojiBottomSheet.kt
index 091ac893ada940f7057369ff912f6c76e0110f90..7d935e93a587656cdf9f3496d5b7c94c514a8dd7 100644
--- a/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/EmojiBottomSheet.kt
+++ b/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/EmojiBottomSheet.kt
@@ -12,10 +12,9 @@ import androidx.navigation.fragment.navArgs
 import com.google.android.material.bottomsheet.BottomSheetBehavior
 import com.google.android.material.bottomsheet.BottomSheetDialog
 import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.vanniktech.emoji.Emoji
 import com.vanniktech.emoji.EmojiTheming
-import com.vanniktech.emoji.recent.NoRecentEmoji
-import com.vanniktech.emoji.search.SearchEmojiManager
-import com.vanniktech.emoji.variant.NoVariantEmoji
+import com.vanniktech.emoji.recent.RecentEmojiManager
 import org.futo.circles.R
 import org.futo.circles.databinding.BottomSheetEmojiBinding
 
@@ -59,7 +58,7 @@ class EmojiBottomSheet : BottomSheetDialogFragment() {
                     requireView(),
                     { emoji ->
                         emojiView
-                        onEmojiSelected(emoji.unicode)
+                        onEmojiSelected(emoji)
                     }, null, null,
                     EmojiTheming(
                         backgroundColor = ContextCompat.getColor(
@@ -83,16 +82,16 @@ class EmojiBottomSheet : BottomSheetDialogFragment() {
                             requireContext(),
                             R.color.gray
                         )
-                    ),
-                    NoRecentEmoji, SearchEmojiManager(), NoVariantEmoji
+                    ), RecentEmojisProvider.get(requireContext())
                 )
                 tearDown()
             }
         }
     }
 
-    private fun onEmojiSelected(unicode: String) {
-        emojiPickerListener?.onEmojiSelected(args.roomId, args.eventId, unicode)
+    private fun onEmojiSelected(emoji: Emoji) {
+        emojiPickerListener?.onEmojiSelected(args.roomId, args.eventId, emoji.unicode)
+        RecentEmojisProvider.addNewEmoji(requireContext(), emoji)
         dismiss()
     }
 
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/EmojisTimelineAdapter.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/EmojisTimelineAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e9b5e2c5e070d82953d8b496e28158211ca9b9e3
--- /dev/null
+++ b/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/EmojisTimelineAdapter.kt
@@ -0,0 +1,25 @@
+package org.futo.circles.feature.timeline.post.emoji
+
+
+import android.view.ViewGroup
+import org.futo.circles.core.list.BaseRvAdapter
+import org.futo.circles.core.model.ReactionsData
+
+
+class EmojisTimelineAdapter(
+    private val onEmojiSelected: (ReactionsData) -> Unit
+) : BaseRvAdapter<ReactionsData, EmojisTimelineViewHolder>(
+    DefaultIdEntityCallback()
+) {
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
+        EmojisTimelineViewHolder(
+            parent,
+            onEmojiClicked = { position -> onEmojiSelected(getItem(position)) })
+
+
+    override fun onBindViewHolder(holder: EmojisTimelineViewHolder, position: Int) {
+        holder.bind(getItem(position))
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/EmojisTimelineViewHolder.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/EmojisTimelineViewHolder.kt
new file mode 100644
index 0000000000000000000000000000000000000000..71986fbac5330fda3c165dfed6af5e6c26e56b6b
--- /dev/null
+++ b/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/EmojisTimelineViewHolder.kt
@@ -0,0 +1,30 @@
+package org.futo.circles.feature.timeline.post.emoji
+
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import org.futo.circles.core.extensions.onClick
+import org.futo.circles.core.list.ViewBindingHolder
+import org.futo.circles.core.model.ReactionsData
+import org.futo.circles.databinding.ListItemTimelineReactionBinding
+
+class EmojisTimelineViewHolder(
+    parent: ViewGroup,
+    private val onEmojiClicked: (Int) -> Unit
+) : RecyclerView.ViewHolder(inflate(parent, ListItemTimelineReactionBinding::inflate)) {
+
+    private companion object : ViewBindingHolder
+
+    private val binding = baseBinding as ListItemTimelineReactionBinding
+
+    init {
+        onClick(binding.emojiChip) { position -> onEmojiClicked(position) }
+    }
+
+    fun bind(data: ReactionsData) {
+        binding.emojiChip.apply {
+            val title = "${data.key} ${data.count}"
+            text = title
+            isChecked = data.addedByMe
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/RecentEmojisProvider.kt b/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/RecentEmojisProvider.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c40a3cf0640645c5fd85b2ddec7716d9b855b0c7
--- /dev/null
+++ b/app/src/main/java/org/futo/circles/feature/timeline/post/emoji/RecentEmojisProvider.kt
@@ -0,0 +1,35 @@
+package org.futo.circles.feature.timeline.post.emoji
+
+import android.content.Context
+import com.vanniktech.emoji.Emoji
+import com.vanniktech.emoji.recent.RecentEmojiManager
+import com.vanniktech.emoji.search.SearchEmojiManager
+
+object RecentEmojisProvider {
+
+    private var manager: RecentEmojiManager? = null
+
+    fun get(context: Context) = manager ?: RecentEmojiManager(context).also { manager = it }
+
+    fun saveDefaultRecentEmojis(context: Context) {
+        val recentManager = get(context)
+        val recent = recentManager.getRecentEmojis()
+        if (recent.isEmpty()) return
+        recentManager.apply {
+            SearchEmojiManager().apply {
+                search("100").firstOrNull()?.emoji?.let { emoji -> addEmoji(emoji) }
+                search("tada").firstOrNull()?.emoji?.let { emoji -> addEmoji(emoji) }
+                search("grinning").firstOrNull()?.emoji?.let { emoji -> addEmoji(emoji) }
+                search("thumbsup").firstOrNull()?.emoji?.let { emoji -> addEmoji(emoji) }
+            }
+            persist()
+        }
+    }
+
+    fun addNewEmoji(context: Context, emoji: Emoji) {
+        get(context).apply {
+            addEmoji(emoji)
+            persist()
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/futo/circles/view/PostFooterView.kt b/app/src/main/java/org/futo/circles/view/PostFooterView.kt
index 7bec29f38c9cc2df1a6ae180d38755c67705d21f..c3ee70f4fd1f498abac12c0177d08c20381cedec 100644
--- a/app/src/main/java/org/futo/circles/view/PostFooterView.kt
+++ b/app/src/main/java/org/futo/circles/view/PostFooterView.kt
@@ -3,16 +3,13 @@ package org.futo.circles.view
 import android.content.Context
 import android.util.AttributeSet
 import android.view.LayoutInflater
-import android.widget.LinearLayout
 import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.content.ContextCompat
 import androidx.core.view.isVisible
-import com.google.android.material.chip.Chip
-import org.futo.circles.R
 import org.futo.circles.core.extensions.setIsVisible
-import org.futo.circles.databinding.ViewPostFooterBinding
 import org.futo.circles.core.model.Post
 import org.futo.circles.core.model.ReactionsData
+import org.futo.circles.databinding.ViewPostFooterBinding
+import org.futo.circles.feature.timeline.post.emoji.EmojisTimelineAdapter
 import org.matrix.android.sdk.api.session.room.powerlevels.Role
 
 
@@ -27,6 +24,16 @@ class PostFooterView(
     private var optionsListener: PostOptionsListener? = null
     private var post: Post? = null
     private var userPowerLevel: Int = Role.Default.value
+    private val emojisTimelineAdapter = EmojisTimelineAdapter { reaction ->
+        post?.let {
+            optionsListener?.onEmojiChipClicked(
+                it.postInfo.roomId,
+                it.id,
+                reaction.key,
+                reaction.addedByMe
+            )
+        }
+    }
 
     init {
         setupViews()
@@ -45,6 +52,7 @@ class PostFooterView(
             btnLike.setOnClickListener {
                 post?.let { optionsListener?.onShowEmoji(it.postInfo.roomId, it.id) }
             }
+            rvEmojis.adapter = emojisTimelineAdapter
         }
     }
 
@@ -76,38 +84,8 @@ class PostFooterView(
     }
 
     private fun bindReactionsList(reactions: List<ReactionsData>) {
-        binding.chipsScrollView.setIsVisible(reactions.isNotEmpty())
-        with(binding.lReactions) {
-            removeAllViews()
-            val layoutParams = LinearLayout.LayoutParams(
-                LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT
-            )
-            layoutParams.setMargins(0, 0, 4, 0)
-
-            reactions.forEach { reaction ->
-                val title = "${reaction.key} ${reaction.count}"
-                addView(Chip(context).apply {
-                    text = title
-                    setOnClickListener {
-                        post?.let {
-                            optionsListener?.onEmojiChipClicked(
-                                it.postInfo.roomId,
-                                it.id,
-                                reaction.key,
-                                reaction.addedByMe
-                            )
-                        }
-                    }
-                    isCheckable = true
-                    isCheckedIconVisible = false
-                    chipBackgroundColor =
-                        ContextCompat.getColorStateList(context, R.color.emoji_chip_background)
-                    setEnsureMinTouchTargetSize(false)
-                    isChecked = reaction.addedByMe
-                }, layoutParams)
-            }
-
-        }
+        binding.rvEmojis.setIsVisible(reactions.isNotEmpty())
+        emojisTimelineAdapter.submitList(reactions)
     }
 
     private fun areUserAbleToPost() = userPowerLevel >= Role.Default.value
diff --git a/app/src/main/res/layout/dialog_fragment_timeline.xml b/app/src/main/res/layout/dialog_fragment_timeline.xml
index 4ba30ce488477c00142667dd28c74f03b32d06cf..3f86ea45d08a1bcbeaf172e3799e35ae79f4929d 100644
--- a/app/src/main/res/layout/dialog_fragment_timeline.xml
+++ b/app/src/main/res/layout/dialog_fragment_timeline.xml
@@ -26,7 +26,7 @@
         app:layout_constraintTop_toBottomOf="@id/toolbar" />
 
 
-    <androidx.recyclerview.widget.RecyclerView
+    <org.futo.circles.core.view.LoadingRecyclerView
         android:id="@+id/rvTimeline"
         android:layout_width="match_parent"
         android:layout_height="0dp"
diff --git a/app/src/main/res/layout/fragment_people.xml b/app/src/main/res/layout/fragment_people.xml
index f8723fa1629336ac142d34a61d806fc392d62e40..df4bc56c2b7fa56aaa626cfbc4ba6abad29c43a8 100644
--- a/app/src/main/res/layout/fragment_people.xml
+++ b/app/src/main/res/layout/fragment_people.xml
@@ -4,7 +4,7 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
-    <androidx.recyclerview.widget.RecyclerView
+    <org.futo.circles.core.view.LoadingRecyclerView
         android:id="@+id/rvUsers"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/app/src/main/res/layout/list_item_timeline_reaction.xml b/app/src/main/res/layout/list_item_timeline_reaction.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e34a4347e8cfbfb50ae0983e200cda369474ca1c
--- /dev/null
+++ b/app/src/main/res/layout/list_item_timeline_reaction.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:paddingHorizontal="1dp"
+    android:paddingVertical="2dp">
+
+    <com.google.android.material.chip.Chip
+        android:id="@+id/emojiChip"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:checkable="true"
+        app:checkedIconVisible="false"
+        app:chipBackgroundColor="@color/emoji_chip_background"
+        app:ensureMinTouchTargetSize="false" />
+</FrameLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/view_post_footer.xml b/app/src/main/res/layout/view_post_footer.xml
index a1bef382bcd6e0957329fc08897dc33422a3cf7a..9fcbace7d2b583f367f11c70c2bb1182b054dafd 100644
--- a/app/src/main/res/layout/view_post_footer.xml
+++ b/app/src/main/res/layout/view_post_footer.xml
@@ -7,23 +7,17 @@
     tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
 
 
-    <HorizontalScrollView
-        android:id="@+id/chipsScrollView"
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rvEmojis"
         android:layout_width="0dp"
         android:layout_height="0dp"
+        android:orientation="horizontal"
+        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
         app:layout_constraintBottom_toBottomOf="@id/btnLike"
         app:layout_constraintEnd_toStartOf="@id/btnLike"
         app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="@id/btnLike">
+        app:layout_constraintTop_toTopOf="@id/btnLike" />
 
-        <LinearLayout
-            android:id="@+id/lReactions"
-            android:layout_width="wrap_content"
-            android:layout_height="match_parent"
-            android:orientation="horizontal"
-            android:paddingVertical="2dp" />
-
-    </HorizontalScrollView>
 
     <com.google.android.material.button.MaterialButton
         android:id="@+id/btnLike"
@@ -43,7 +37,7 @@
         app:iconTint="@color/button_src_state_color"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintEnd_toStartOf="@id/btnReply"
-        app:layout_constraintStart_toEndOf="@id/chipsScrollView"
+        app:layout_constraintStart_toEndOf="@id/rvEmojis"
         app:layout_constraintTop_toBottomOf="@id/divider"
         app:tint="@color/button_src_state_color" />
 
@@ -60,9 +54,9 @@
         android:textAlignment="gravity"
         android:textColor="@color/button_src_state_color"
         android:textSize="14sp"
-        app:iconPadding="2dp"
         app:icon="@drawable/ic_reply"
         app:iconGravity="textStart"
+        app:iconPadding="2dp"
         app:iconSize="20dp"
         app:iconTint="@color/button_src_state_color"
         app:layout_constraintBottom_toBottomOf="@id/btnLike"
diff --git a/core/src/main/java/org/futo/circles/core/model/ReactionsData.kt b/core/src/main/java/org/futo/circles/core/model/ReactionsData.kt
index 5c8fc71f39f2abec33d37b8efbdf26bfb4fcbc3d..a10ef9a59dce6fd47b940af700598302e9577311 100644
--- a/core/src/main/java/org/futo/circles/core/model/ReactionsData.kt
+++ b/core/src/main/java/org/futo/circles/core/model/ReactionsData.kt
@@ -1,7 +1,11 @@
 package org.futo.circles.core.model
 
+import org.futo.circles.core.list.IdEntity
+
 data class ReactionsData(
     val key: String,
     val count: Int,
     val addedByMe: Boolean
-)
\ No newline at end of file
+) : IdEntity<String> {
+    override val id: String = key
+}
\ No newline at end of file
diff --git a/core/src/main/java/org/futo/circles/core/view/LoadingRecyclerView.kt b/core/src/main/java/org/futo/circles/core/view/LoadingRecyclerView.kt
new file mode 100644
index 0000000000000000000000000000000000000000..47fd9f1bb0895e395cc6734fa2db17213288ab07
--- /dev/null
+++ b/core/src/main/java/org/futo/circles/core/view/LoadingRecyclerView.kt
@@ -0,0 +1,60 @@
+package org.futo.circles.core.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.recyclerview.widget.RecyclerView
+import androidx.recyclerview.widget.RecyclerView.ItemDecoration
+import com.google.android.material.floatingactionbutton.FloatingActionButton
+import org.futo.circles.core.databinding.ViewLoadingRecyclerBinding
+import org.futo.circles.core.extensions.bindToFab
+import org.futo.circles.core.extensions.gone
+import org.futo.circles.core.extensions.visible
+import org.matrix.android.sdk.api.extensions.tryOrNull
+
+class LoadingRecyclerView(
+    context: Context,
+    attrs: AttributeSet? = null
+) : ConstraintLayout(context, attrs) {
+
+    private val binding = ViewLoadingRecyclerBinding.inflate(LayoutInflater.from(context), this)
+
+    private val dataObserver = object : RecyclerView.AdapterDataObserver() {
+        override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
+            super.onItemRangeInserted(positionStart, itemCount)
+            binding.vLoading.gone()
+            tryOrNull { binding.rvList.adapter?.unregisterAdapterDataObserver(this) }
+        }
+    }
+
+    var adapter: RecyclerView.Adapter<*>? = null
+        get() = binding.rvList.adapter
+        set(value) {
+            binding.rvList.adapter = value
+            field = value
+            setupDataObserver()
+        }
+
+
+    fun addItemDecoration(decoration: ItemDecoration) {
+        binding.rvList.addItemDecoration(decoration)
+    }
+
+    fun getRecyclerView() = binding.rvList
+
+    fun bindToFab(fab: FloatingActionButton) = binding.rvList.bindToFab(fab)
+
+    private fun setupDataObserver() {
+        with(binding) {
+            val initialCount = rvList.adapter?.itemCount ?: 0
+            if (initialCount == 0) {
+                vLoading.visible()
+                rvList.adapter?.registerAdapterDataObserver(dataObserver)
+            } else {
+                tryOrNull { rvList.adapter?.unregisterAdapterDataObserver(dataObserver) }
+                vLoading.gone()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/core/src/main/res/layout/fragment_rooms.xml b/core/src/main/res/layout/fragment_rooms.xml
index 4d85a6f00db6f3457310e3c30b07f4199c907657..f1ecdf1d176c6ac299d5f3fe9181818f663ee305 100644
--- a/core/src/main/res/layout/fragment_rooms.xml
+++ b/core/src/main/res/layout/fragment_rooms.xml
@@ -1,11 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
 <FrameLayout 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="match_parent"
-    android:layout_height="match_parent"
-    xmlns:tools="http://schemas.android.com/tools">
+    android:layout_height="match_parent">
 
-    <androidx.recyclerview.widget.RecyclerView
+    <org.futo.circles.core.view.LoadingRecyclerView
         android:id="@+id/rvRooms"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
diff --git a/core/src/main/res/layout/fragment_select_users.xml b/core/src/main/res/layout/fragment_select_users.xml
index 20fa86ed9d85a05815adaf9fc3e07918e186824c..47fb23940e6dd8d148843373abbabf086572ce20 100644
--- a/core/src/main/res/layout/fragment_select_users.xml
+++ b/core/src/main/res/layout/fragment_select_users.xml
@@ -38,7 +38,7 @@
         app:layout_constraintTop_toBottomOf="@id/rvSelectedUsers"
         tools:visibility="visible" />
 
-    <androidx.recyclerview.widget.RecyclerView
+    <org.futo.circles.core.view.LoadingRecyclerView
         android:id="@+id/rvUsers"
         android:layout_width="0dp"
         android:layout_height="0dp"
diff --git a/core/src/main/res/layout/view_loading_recycler.xml b/core/src/main/res/layout/view_loading_recycler.xml
new file mode 100644
index 0000000000000000000000000000000000000000..85d892941ee43b9f95a2a8e81d78a2fb6844daae
--- /dev/null
+++ b/core/src/main/res/layout/view_loading_recycler.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge 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="match_parent"
+    android:layout_height="wrap_content"
+    tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
+
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rvList"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
+
+    <ProgressBar
+        android:id="@+id/vLoading"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="@id/rvList"
+        app:layout_constraintEnd_toEndOf="@id/rvList"
+        app:layout_constraintStart_toStartOf="@id/rvList"
+        app:layout_constraintTop_toTopOf="@id/rvList"
+        tools:visibility="visible" />
+
+</merge>
\ No newline at end of file