diff --git a/app/src/main/java/com/futo/circles/base/BaseRecycleViewDecoration.kt b/app/src/main/java/com/futo/circles/base/BaseRecycleViewDecoration.kt
new file mode 100644
index 0000000000000000000000000000000000000000..6bec72b6f483ed325be41be998fe1640404db441
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/base/BaseRecycleViewDecoration.kt
@@ -0,0 +1,89 @@
+package com.futo.circles.base
+
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.view.View
+import androidx.recyclerview.widget.RecyclerView
+
+
+abstract class BaseRvDecoration<in T : RecyclerView.ViewHolder> : RecyclerView.ItemDecoration() {
+
+    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) =
+        if (shouldDraw()) draw(c, parent, state) else super.onDraw(c, parent, state)
+
+    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) =
+        if (shouldDrawOver()) drawOver(c, parent, state) else super.onDrawOver(c, parent, state)
+
+    override fun getItemOffsets(
+        outRect: Rect,
+        view: View,
+        parent: RecyclerView,
+        state: RecyclerView.State
+    ) {
+        @Suppress("UNCHECKED_CAST")
+        val holder = parent.getChildViewHolder(view) as? T
+
+        holder?.takeIf(::shouldOffsetHolder)?.let { offsetHolder(outRect, holder, parent, state) }
+            ?: super.getItemOffsets(outRect, view, parent, state)
+    }
+
+    open fun shouldDraw(): Boolean = false
+    open fun shouldDrawOver(): Boolean = false
+    open fun shouldOffsetHolder(holder: T): Boolean = false
+
+    open fun draw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
+
+    }
+
+    open fun drawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
+
+    }
+
+    open fun offsetHolder(
+        outRect: Rect,
+        holder: T,
+        parent: RecyclerView,
+        state: RecyclerView.State
+    ) {
+
+    }
+
+    class OffsetDecoration<in T : RecyclerView.ViewHolder>(
+        private val topOffset: Int = 0,
+        private val bottomOffset: Int = 0,
+        private val leftOffset: Int = 0,
+        private val rightOffset: Int = 0
+    ) : BaseRvDecoration<T>() {
+        override fun shouldOffsetHolder(holder: T): Boolean = true
+
+        constructor(verticalOffset: Int, horizontalOffset: Int) : this(
+            topOffset = verticalOffset,
+            bottomOffset = verticalOffset,
+            leftOffset = horizontalOffset,
+            rightOffset = horizontalOffset
+        )
+
+        constructor(offset: Int) : this(
+            topOffset = offset,
+            bottomOffset = offset,
+            leftOffset = offset,
+            rightOffset = offset
+        )
+
+        override fun offsetHolder(
+            outRect: Rect,
+            holder: T,
+            parent: RecyclerView,
+            state: RecyclerView.State
+        ) {
+            outRect.left = leftOffset
+            outRect.right = rightOffset
+            outRect.top = topOffset
+            outRect.bottom = bottomOffset
+        }
+    }
+
+}
+
+
+
diff --git a/app/src/main/java/com/futo/circles/base/BaseRecyclerView.kt b/app/src/main/java/com/futo/circles/base/BaseRecyclerView.kt
index 09f7511aae85d929466b873633c1deee73b1c220..c89df71651d17e1d4eaf50b8a8c54df8f23b44aa 100644
--- a/app/src/main/java/com/futo/circles/base/BaseRecyclerView.kt
+++ b/app/src/main/java/com/futo/circles/base/BaseRecyclerView.kt
@@ -3,26 +3,29 @@ package com.futo.circles.base
 import android.annotation.SuppressLint
 import android.content.Context
 import android.view.LayoutInflater
+import android.view.View
 import android.view.ViewGroup
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.ListAdapter
 import androidx.recyclerview.widget.RecyclerView
 import androidx.viewbinding.ViewBinding
 
-abstract class BaseRecyclerViewHolder<T, VB : ViewBinding> : RecyclerView.ViewHolder {
 
-    protected val binding: VB
+interface ViewBindingHolder{
 
-    protected constructor(
-        parent: ViewGroup,
-        inflate: (LayoutInflater, ViewGroup?, Boolean) -> VB
-    ) : this(inflate.invoke(LayoutInflater.from(parent.context), parent, false))
+    val baseBinding: ViewBinding get() = viewBinding
 
-    private constructor(viewBinding: VB) : super(viewBinding.root) {
-        this.binding = viewBinding
+    fun inflate(
+        parent: ViewGroup,
+        inflate: (LayoutInflater, ViewGroup?, Boolean) -> ViewBinding
+    ): View {
+        viewBinding = inflate.invoke(LayoutInflater.from(parent.context), parent, false)
+        return viewBinding.root
     }
 
-    abstract fun bind(data: T)
+    private companion object {
+        private lateinit var viewBinding: ViewBinding
+    }
 }
 
 val RecyclerView.ViewHolder.context: Context get() = this.itemView.context
@@ -31,16 +34,24 @@ abstract class BaseRvAdapter<T, VH : RecyclerView.ViewHolder>(
     itemCallback: DiffUtil.ItemCallback<T>
 ) : ListAdapter<T, VH>(itemCallback) {
 
-    @Suppress("UNCHECKED_CAST")
-    protected fun <D : T> getItemAs(position: Int): D = getItem(position) as D
-
     companion object {
         @Suppress("FunctionName")
         @SuppressLint("DiffUtilEquals")
-        fun <T> DefaultDiffUtilCallback() = object : DiffUtil.ItemCallback<T>() {
-            override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = oldItem == newItem
+        fun <T : IdEntity<*>> DefaultIdEntityCallback() = object : DiffUtil.ItemCallback<T>() {
+            override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = oldItem.id == newItem.id
             override fun areContentsTheSame(oldItem: T, newItem: T): Boolean = oldItem == newItem
         }
+
+        @Suppress("FunctionName")
+        @SuppressLint("DiffUtilEquals")
+        fun <T : IdEntity<*>, C> PayloadIdEntityCallback(
+            payload: (old: T, new: T) -> C?
+        ) = object : DiffUtil.ItemCallback<T>() {
+            override fun areItemsTheSame(oldItem: T, newItem: T): Boolean = oldItem.id == newItem.id
+            override fun areContentsTheSame(oldItem: T, newItem: T): Boolean = oldItem == newItem
+
+            override fun getChangePayload(oldItem: T, newItem: T) = payload(oldItem, newItem)
+        }
     }
 
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/base/IdEntity.kt b/app/src/main/java/com/futo/circles/base/IdEntity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..7004f2f979a7b29354bd2644596a5cfbf128bdb5
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/base/IdEntity.kt
@@ -0,0 +1,5 @@
+package com.futo.circles.base
+
+interface IdEntity<out IdClass> {
+    val id: IdClass
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/di/DataSourceModule.kt b/app/src/main/java/com/futo/circles/di/DataSourceModule.kt
index a2e49b52fe9d9373114bff989daf4de2620a50c7..e570f965d3adbbb95b33f9c975b4e57122a467fa 100644
--- a/app/src/main/java/com/futo/circles/di/DataSourceModule.kt
+++ b/app/src/main/java/com/futo/circles/di/DataSourceModule.kt
@@ -1,8 +1,14 @@
 package com.futo.circles.di
 
+import com.futo.circles.ui.groups.timeline.data_source.GroupTimelineBuilder
+import com.futo.circles.ui.groups.timeline.data_source.GroupTimelineDatasource
 import com.futo.circles.ui.log_in.data_source.LoginDataSource
 import org.koin.dsl.module
 
 val dataSourceModule = module {
-    factory { LoginDataSource(get()) }
+    factory { LoginDataSource(get(), get()) }
+
+    factory { (roomId: String) -> GroupTimelineDatasource(roomId, get(), get()) }
+
+    factory { GroupTimelineBuilder() }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/di/UiModule.kt b/app/src/main/java/com/futo/circles/di/UiModule.kt
index e57fbe50965985dc0e9c6251a4f7d49d5524ef7e..8b5d91860061f3954b686123e4a240442abebf20 100644
--- a/app/src/main/java/com/futo/circles/di/UiModule.kt
+++ b/app/src/main/java/com/futo/circles/di/UiModule.kt
@@ -4,10 +4,11 @@ import com.futo.circles.ui.groups.GroupsViewModel
 import com.futo.circles.ui.groups.timeline.GroupTimelineViewModel
 import com.futo.circles.ui.log_in.LogInViewModel
 import org.koin.androidx.viewmodel.dsl.viewModel
+import org.koin.core.parameter.parametersOf
 import org.koin.dsl.module
 
 val uiModule = module {
     viewModel { LogInViewModel(get()) }
     viewModel { GroupsViewModel(get()) }
-    viewModel { (roomId: String) -> GroupTimelineViewModel(roomId, get()) }
+    viewModel { (roomId: String) -> GroupTimelineViewModel(get { parametersOf(roomId) }) }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/extensions/ContextExtensions.kt b/app/src/main/java/com/futo/circles/extensions/ContextExtensions.kt
new file mode 100644
index 0000000000000000000000000000000000000000..53b4b0c7493b083d4dadef65f34ff2de3b4de7d3
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/extensions/ContextExtensions.kt
@@ -0,0 +1,6 @@
+package com.futo.circles.extensions
+
+import android.content.Context
+import android.support.annotation.DimenRes
+
+fun Context.dimen(@DimenRes resource: Int): Int = resources.getDimensionPixelSize(resource)
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/extensions/ImageViewExtensions.kt b/app/src/main/java/com/futo/circles/extensions/ImageViewExtensions.kt
index fb1f33b9e18e2c695db1a49b5e51d13698a556b0..c2b3ccf7b4a60130febb62c464ee2201e2f77230 100644
--- a/app/src/main/java/com/futo/circles/extensions/ImageViewExtensions.kt
+++ b/app/src/main/java/com/futo/circles/extensions/ImageViewExtensions.kt
@@ -1,6 +1,7 @@
 package com.futo.circles.extensions
 
 import android.widget.ImageView
+import com.futo.circles.R
 import com.squareup.picasso.Picasso
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
 
@@ -17,4 +18,8 @@ fun ImageView.loadMatrixThumbnail(
         ContentUrlResolver.ThumbnailMethod.SCALE
     )
     Picasso.get().load(resolvedUrl).into(this)
+}
+
+fun ImageView.setIsEncryptedIcon(isEncrypted: Boolean) {
+    setImageResource(if (isEncrypted) R.drawable.ic_lock else R.drawable.ic_lock_open)
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/extensions/MatrixRoomExtensions.kt b/app/src/main/java/com/futo/circles/extensions/MatrixRoomExtensions.kt
index d0797744aa234b43d028dbcc3b54e62046f10ced..f5d674f0af5dbb9e656ad6f5c6bc9c4566c34d86 100644
--- a/app/src/main/java/com/futo/circles/extensions/MatrixRoomExtensions.kt
+++ b/app/src/main/java/com/futo/circles/extensions/MatrixRoomExtensions.kt
@@ -1,9 +1,11 @@
 package com.futo.circles.extensions
 
+import com.futo.circles.mapping.toGroupListItem
 import org.matrix.android.sdk.api.session.room.model.RoomSummary
 
-fun List<RoomSummary>.containsTag(tagName: String) = filter { room ->
-    room.tags.firstOrNull { tag -> tag.name.contains(tagName) }?.let { true } ?: false
+fun List<RoomSummary>.toGroupsList(tagName: String) = mapNotNull { room ->
+    val isGroup = room.tags.firstOrNull { tag -> tag.name.contains(tagName) }?.let { true } ?: false
+    if (isGroup) room.toGroupListItem() else null
 }
 
 fun RoomSummary.nameOrId() = displayName.takeIf { it.isNotEmpty() } ?: roomId
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/extensions/RecyclerViewExtensions.kt b/app/src/main/java/com/futo/circles/extensions/RecyclerViewExtensions.kt
index cb01b978ce9b9c6f0a98d5574bb157c557f7b007..7eddf12ac4c7dff44dbffeccfa7949a9389dac1f 100644
--- a/app/src/main/java/com/futo/circles/extensions/RecyclerViewExtensions.kt
+++ b/app/src/main/java/com/futo/circles/extensions/RecyclerViewExtensions.kt
@@ -2,7 +2,20 @@ package com.futo.circles.extensions
 
 
 import android.view.View
+import androidx.core.view.isVisible
 import androidx.recyclerview.widget.RecyclerView
+import com.google.android.material.floatingactionbutton.FloatingActionButton
 
 fun RecyclerView.ViewHolder.onClick(view: View, perform: (adapterPosition: Int) -> Unit) =
-    view.setOnClickListener { bindingAdapterPosition.takeIf { it != -1 }?.let(perform) }
\ No newline at end of file
+    view.setOnClickListener { bindingAdapterPosition.takeIf { it != -1 }?.let(perform) }
+
+// Kotlin
+fun RecyclerView.bindToFab(fab:FloatingActionButton) {
+    addOnScrollListener(object : RecyclerView.OnScrollListener() {
+        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+            super.onScrolled(recyclerView, dx, dy)
+            if (dy > 0 && fab.isVisible) fab.hide()
+            else if (dy < 0 && fab.visibility != View.VISIBLE) fab.show()
+        }
+    })
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/mapping/RoomSummaryMapping.kt b/app/src/main/java/com/futo/circles/mapping/RoomSummaryMapping.kt
new file mode 100644
index 0000000000000000000000000000000000000000..b81d887cfa4ea6111cfbfbffca7c1014db50267f
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/mapping/RoomSummaryMapping.kt
@@ -0,0 +1,15 @@
+package com.futo.circles.mapping
+
+import com.futo.circles.extensions.nameOrId
+import com.futo.circles.model.GroupListItem
+import org.matrix.android.sdk.api.session.room.model.RoomSummary
+
+fun RoomSummary.toGroupListItem() = GroupListItem(
+    id = roomId,
+    title = nameOrId(),
+    topic = topic,
+    membersCount = joinedMembersCount ?: 0,
+    timestamp = latestPreviewableEvent?.root?.originServerTs ?: System.currentTimeMillis(),
+    isEncrypted = isEncrypted,
+    avatarUrl = avatarUrl
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/mapping/TimelineEventMapping.kt b/app/src/main/java/com/futo/circles/mapping/TimelineEventMapping.kt
new file mode 100644
index 0000000000000000000000000000000000000000..e6474a0339031552ccf869ba18ddc3ece4f04779
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/mapping/TimelineEventMapping.kt
@@ -0,0 +1,51 @@
+package com.futo.circles.mapping
+
+import com.futo.circles.model.*
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.message.MessageImageContent
+import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.api.session.room.timeline.getRelationContent
+import org.matrix.android.sdk.api.session.room.timeline.isReply
+
+
+fun TimelineEvent.toPost(
+    postContentType: PostContentType,
+    isRepliesVisible: Boolean = false
+): Post =
+    if (isReply()) toReplyPost(postContentType) else toRootPost(postContentType, isRepliesVisible)
+
+private fun TimelineEvent.toPostInfo(): PostInfo = PostInfo(
+    id = eventId,
+    isEncrypted = isEncrypted(),
+    timestamp = root.originServerTs ?: System.currentTimeMillis(),
+    sender = senderInfo
+)
+
+private fun TimelineEvent.toRootPost(postContentType: PostContentType, isRepliesVisible: Boolean) =
+    RootPost(
+        postInfo = toPostInfo(),
+        content = toPostContent(postContentType),
+        isRepliesVisible = isRepliesVisible,
+    )
+
+private fun TimelineEvent.toReplyPost(postContentType: PostContentType) = ReplyPost(
+    postInfo = toPostInfo(),
+    content = toPostContent(postContentType),
+    replyToId = getRelationContent()?.inReplyTo?.eventId ?: "",
+)
+
+private fun TimelineEvent.toPostContent(postContentType: PostContentType): PostContent =
+    when (postContentType) {
+        PostContentType.TEXT_CONTENT -> toTextContent()
+        PostContentType.IMAGE_CONTENT -> toImageContent()
+    }
+
+private fun TimelineEvent.toTextContent(): TextContent = TextContent(
+    message = root.getClearContent().toModel<MessageTextContent>()?.body ?: ""
+)
+
+private fun TimelineEvent.toImageContent(): ImageContent = ImageContent(
+    url = root.getClearContent()
+        .toModel<MessageImageContent>()?.info?.thumbnailFile?.url ?: ""
+)
diff --git a/app/src/main/java/com/futo/circles/model/GroupListItem.kt b/app/src/main/java/com/futo/circles/model/GroupListItem.kt
new file mode 100644
index 0000000000000000000000000000000000000000..9308a0fdf39e9eca55a88725fde439eb2c8018b4
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/model/GroupListItem.kt
@@ -0,0 +1,13 @@
+package com.futo.circles.model
+
+import com.futo.circles.base.IdEntity
+
+data class GroupListItem(
+    override val id: String,
+    val title: String,
+    val topic: String,
+    val isEncrypted: Boolean,
+    val avatarUrl: String,
+    val membersCount: Int,
+    val timestamp: Long
+) : IdEntity<String>
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/model/GroupListItemPayload.kt b/app/src/main/java/com/futo/circles/model/GroupListItemPayload.kt
new file mode 100644
index 0000000000000000000000000000000000000000..edb4c6214700f46853aab959855ca9bbf12a000c
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/model/GroupListItemPayload.kt
@@ -0,0 +1,6 @@
+package com.futo.circles.model
+
+data class GroupListItemPayload(
+    val membersCount: Int,
+    val timestamp: Long
+)
diff --git a/app/src/main/java/com/futo/circles/model/Post.kt b/app/src/main/java/com/futo/circles/model/Post.kt
new file mode 100644
index 0000000000000000000000000000000000000000..bc53ea76428b0f6ba82bd9a4a765b66bb22d7df0
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/model/Post.kt
@@ -0,0 +1,26 @@
+package com.futo.circles.model
+
+import com.futo.circles.base.IdEntity
+
+sealed class Post(
+    open val postInfo: PostInfo,
+    open val content: PostContent
+) : IdEntity<String> {
+    override val id: String get() = postInfo.id
+}
+
+data class RootPost(
+    override val postInfo: PostInfo,
+    override val content: PostContent,
+    val replies: List<ReplyPost> = emptyList(),
+    val isRepliesVisible: Boolean = false
+) : Post(postInfo, content) {
+    fun hasReplies(): Boolean = replies.isNotEmpty()
+    fun getRepliesCount(): Int = replies.size
+}
+
+data class ReplyPost(
+    override val postInfo: PostInfo,
+    override val content: PostContent,
+    val replyToId: String
+) : Post(postInfo, content)
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/model/PostContent.kt b/app/src/main/java/com/futo/circles/model/PostContent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..04b63f497342a597b694ebf1812e7991ee361de9
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/model/PostContent.kt
@@ -0,0 +1,17 @@
+package com.futo.circles.model
+
+import org.matrix.android.sdk.api.session.room.model.message.MessageType
+
+enum class PostContentType(val typeKey: String) {
+    TEXT_CONTENT(MessageType.MSGTYPE_TEXT), IMAGE_CONTENT(MessageType.MSGTYPE_IMAGE)
+}
+
+sealed class PostContent(val type: PostContentType)
+
+data class TextContent(
+    val message: String
+) : PostContent(PostContentType.TEXT_CONTENT)
+
+data class ImageContent(
+    val url: String
+) : PostContent(PostContentType.IMAGE_CONTENT)
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/model/PostInfo.kt b/app/src/main/java/com/futo/circles/model/PostInfo.kt
new file mode 100644
index 0000000000000000000000000000000000000000..cb2661abd1e9c7e59ce78414ce70d7aabafe7a51
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/model/PostInfo.kt
@@ -0,0 +1,10 @@
+package com.futo.circles.model
+
+import org.matrix.android.sdk.api.session.room.sender.SenderInfo
+
+data class PostInfo(
+    val id: String,
+    val sender: SenderInfo,
+    val isEncrypted: Boolean,
+    val timestamp: Long
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/model/PostItemPayload.kt b/app/src/main/java/com/futo/circles/model/PostItemPayload.kt
new file mode 100644
index 0000000000000000000000000000000000000000..613d557000023f3bcc4507dfc19a4b40525b546b
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/model/PostItemPayload.kt
@@ -0,0 +1,7 @@
+package com.futo.circles.model
+
+class PostItemPayload(
+    val repliesCount: Int,
+    val isRepliesVisible: Boolean,
+    val hasReplies: Boolean
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/provider/MatrixProvider.kt b/app/src/main/java/com/futo/circles/provider/MatrixProvider.kt
deleted file mode 100644
index 6d29dd1fce264241531dffbf2d4c9749215dc1b1..0000000000000000000000000000000000000000
--- a/app/src/main/java/com/futo/circles/provider/MatrixProvider.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.futo.circles.provider
-
-import org.matrix.android.sdk.api.Matrix
-
-object MatrixProvider {
-    lateinit var matrix: Matrix
-        private set
-
-    fun saveMatrixInstance(matrixInstance: Matrix) {
-        matrix = matrixInstance
-    }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/provider/MatrixSessionProvider.kt b/app/src/main/java/com/futo/circles/provider/MatrixSessionProvider.kt
index 6671e623ca14ea8d7a96d929e542e322df6bd776..6344e1e54d7283f630324959debc9dfed2723589 100644
--- a/app/src/main/java/com/futo/circles/provider/MatrixSessionProvider.kt
+++ b/app/src/main/java/com/futo/circles/provider/MatrixSessionProvider.kt
@@ -16,11 +16,9 @@ class MatrixSessionProvider(private val context: Context) {
                 roomDisplayNameFallbackProvider = RoomDisplayNameFallbackProviderImpl()
             )
         )
-        val matrixInstance =
-            Matrix.getInstance(context).also { MatrixProvider.saveMatrixInstance(it) }
 
         val lastSession =
-            matrixInstance.authenticationService().getLastAuthenticatedSession()
+            Matrix.getInstance(context).authenticationService().getLastAuthenticatedSession()
 
         lastSession?.let { startSession(it) }
     }
diff --git a/app/src/main/java/com/futo/circles/ui/groups/GroupsFragment.kt b/app/src/main/java/com/futo/circles/ui/groups/GroupsFragment.kt
index 9d60a949ad5efde4883f37264008395db9b077c7..36620e2708edfe93b2fd835435cbee0d91bd508d 100644
--- a/app/src/main/java/com/futo/circles/ui/groups/GroupsFragment.kt
+++ b/app/src/main/java/com/futo/circles/ui/groups/GroupsFragment.kt
@@ -9,11 +9,9 @@ import by.kirich1409.viewbindingdelegate.viewBinding
 import com.futo.circles.R
 import com.futo.circles.databinding.GroupsFragmentBinding
 import com.futo.circles.extensions.observeData
-import com.futo.circles.provider.MatrixSessionProvider
+import com.futo.circles.model.GroupListItem
 import com.futo.circles.ui.groups.list.GroupsListAdapter
-import org.koin.android.ext.android.get
 import org.koin.androidx.viewmodel.ext.android.viewModel
-import org.matrix.android.sdk.api.session.room.model.RoomSummary
 
 
 class GroupsFragment : Fragment(R.layout.groups_fragment) {
@@ -22,7 +20,7 @@ class GroupsFragment : Fragment(R.layout.groups_fragment) {
     private val binding by viewBinding(GroupsFragmentBinding::bind)
     private val listAdapter by lazy {
         GroupsListAdapter(
-            get<MatrixSessionProvider>().currentSession?.contentUrlResolver(),
+            viewModel.getContentResolver(),
             ::onGroupListItemClicked
         )
     }
@@ -38,15 +36,13 @@ class GroupsFragment : Fragment(R.layout.groups_fragment) {
         viewModel.groupsLiveData?.observeData(this, ::setGroupsList)
     }
 
-    private fun setGroupsList(list: List<RoomSummary>) {
+    private fun setGroupsList(list: List<GroupListItem>) {
         listAdapter.submitList(list)
     }
 
-    private fun onGroupListItemClicked(room: RoomSummary) {
+    private fun onGroupListItemClicked(room: GroupListItem) {
         findNavController().navigate(
-            GroupsFragmentDirections.actionGroupsFragment2ToGroupTimelineFragment(
-                room.roomId
-            )
+            GroupsFragmentDirections.actionGroupsFragment2ToGroupTimelineFragment(room.id)
         )
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/groups/GroupsViewModel.kt b/app/src/main/java/com/futo/circles/ui/groups/GroupsViewModel.kt
index 0a0776ee6c046d8dec9001cd62a7c659cad2d325..a0b313431dd17234c33a0208f6e2bfe28bec6d68 100644
--- a/app/src/main/java/com/futo/circles/ui/groups/GroupsViewModel.kt
+++ b/app/src/main/java/com/futo/circles/ui/groups/GroupsViewModel.kt
@@ -2,17 +2,19 @@ package com.futo.circles.ui.groups
 
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.map
-import com.futo.circles.extensions.containsTag
+import com.futo.circles.extensions.toGroupsList
 import com.futo.circles.provider.MatrixSessionProvider
 import com.futo.circles.utils.GROUP_TAG
 import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
 
 
 class GroupsViewModel(
-    matrixSessionProvider: MatrixSessionProvider
+    private val matrixSessionProvider: MatrixSessionProvider
 ) : ViewModel() {
 
     val groupsLiveData =
         matrixSessionProvider.currentSession?.getRoomSummariesLive(roomSummaryQueryParams())
-            ?.map { list -> list.containsTag(GROUP_TAG) }
+            ?.map { list -> list.toGroupsList(GROUP_TAG) }
+
+    fun getContentResolver() = matrixSessionProvider.currentSession?.contentUrlResolver()
 }
diff --git a/app/src/main/java/com/futo/circles/ui/groups/list/GroupViewHolder.kt b/app/src/main/java/com/futo/circles/ui/groups/list/GroupViewHolder.kt
index 57bd8dac82e94de52c5ebd93ac9b9d9fe796fbaf..f27d1fd318dfc53bbf7270736e9cb595acbe3888 100644
--- a/app/src/main/java/com/futo/circles/ui/groups/list/GroupViewHolder.kt
+++ b/app/src/main/java/com/futo/circles/ui/groups/list/GroupViewHolder.kt
@@ -2,57 +2,68 @@ package com.futo.circles.ui.groups.list
 
 import android.text.format.DateUtils
 import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
 import com.futo.circles.R
-import com.futo.circles.base.BaseRecyclerViewHolder
+import com.futo.circles.base.ViewBindingHolder
 import com.futo.circles.base.context
 import com.futo.circles.databinding.GroupListItemBinding
 import com.futo.circles.extensions.loadMatrixThumbnail
-import com.futo.circles.extensions.nameOrId
 import com.futo.circles.extensions.onClick
+import com.futo.circles.extensions.setIsEncryptedIcon
+import com.futo.circles.model.GroupListItem
+import com.futo.circles.model.GroupListItemPayload
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
-import org.matrix.android.sdk.api.session.room.model.RoomSummary
 
 class GroupViewHolder(
     parent: ViewGroup,
     private val urlResolver: ContentUrlResolver?,
     onGroupClicked: (Int) -> Unit
-) : BaseRecyclerViewHolder<RoomSummary, GroupListItemBinding>(
-    parent,
-    GroupListItemBinding::inflate
-) {
+) : RecyclerView.ViewHolder(inflate(parent, GroupListItemBinding::inflate)) {
+
+    private companion object : ViewBindingHolder
+
+    private val binding = baseBinding as GroupListItemBinding
 
     init {
         onClick(itemView) { position -> onGroupClicked(position) }
     }
 
-    override fun bind(data: RoomSummary) {
+    fun bind(data: GroupListItem) {
         with(binding) {
             ivGroup.loadMatrixThumbnail(data.avatarUrl, urlResolver)
 
-            ivLock.setImageResource(if (data.isEncrypted) R.drawable.ic_lock else R.drawable.ic_lock_open)
+            ivLock.setIsEncryptedIcon(data.isEncrypted)
 
-            tvGroupTitle.text = data.nameOrId()
-            
-            val membersCount = data.joinedMembersCount ?: 0
-            tvMembers.text = context.resources.getQuantityString(
-                R.plurals.member_plurals,
-                membersCount, membersCount
-            )
+            tvGroupTitle.text = data.title
+
+            setMembersCount(data.membersCount)
 
             tvTopic.text = context.getString(
                 R.string.topic_formatter,
                 data.topic.takeIf { it.isNotEmpty() } ?: context.getString(R.string.none)
             )
 
-            data.latestPreviewableEvent?.root?.originServerTs?.let { time ->
-                tvUpdateTime.text = context.getString(
-                    R.string.last_updated_formatter, DateUtils.getRelativeTimeSpanString(
-                        time,
-                        System.currentTimeMillis(),
-                        DateUtils.HOUR_IN_MILLIS
-                    )
-                )
-            }
+            setUpdateTime(data.timestamp)
         }
     }
+
+    fun bindPayload(data: GroupListItemPayload) {
+        setMembersCount(data.membersCount)
+        setUpdateTime(data.timestamp)
+    }
+
+    private fun setMembersCount(membersCount: Int) {
+        binding.tvMembers.text = context.resources.getQuantityString(
+            R.plurals.member_plurals,
+            membersCount, membersCount
+        )
+    }
+
+    private fun setUpdateTime(timestamp: Long) {
+        binding.tvUpdateTime.text = context.getString(
+            R.string.last_updated_formatter, DateUtils.getRelativeTimeSpanString(
+                timestamp, System.currentTimeMillis(), DateUtils.SECOND_IN_MILLIS
+            )
+        )
+    }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/groups/list/GroupsListAdapter.kt b/app/src/main/java/com/futo/circles/ui/groups/list/GroupsListAdapter.kt
index 6395ce4e8a110071824f0d29087d0a4bd387efc4..bd02c75631ce659276e24293654f75f2970227f8 100644
--- a/app/src/main/java/com/futo/circles/ui/groups/list/GroupsListAdapter.kt
+++ b/app/src/main/java/com/futo/circles/ui/groups/list/GroupsListAdapter.kt
@@ -2,25 +2,44 @@ package com.futo.circles.ui.groups.list
 
 import android.view.ViewGroup
 import com.futo.circles.base.BaseRvAdapter
+import com.futo.circles.model.GroupListItem
+import com.futo.circles.model.GroupListItemPayload
 import org.matrix.android.sdk.api.session.content.ContentUrlResolver
-import org.matrix.android.sdk.api.session.room.model.RoomSummary
 
 class GroupsListAdapter(
     private val urlResolver: ContentUrlResolver?,
-    private val onGroupClicked: (RoomSummary) -> Unit
-) : BaseRvAdapter<RoomSummary, GroupViewHolder>(DefaultDiffUtilCallback()) {
-
+    private val onGroupClicked: (GroupListItem) -> Unit
+) : BaseRvAdapter<GroupListItem, GroupViewHolder>(PayloadIdEntityCallback { _, new ->
+    GroupListItemPayload(
+        membersCount = new.membersCount,
+        timestamp = new.timestamp
+    )
+}) {
     override fun onCreateViewHolder(
         parent: ViewGroup,
         viewType: Int
     ): GroupViewHolder = GroupViewHolder(
         parent = parent,
         urlResolver = urlResolver,
-        onGroupClicked = { position -> getItem(position)?.let { onGroupClicked(it) } }
+        onGroupClicked = { position -> onGroupClicked(getItem(position)) }
     )
 
     override fun onBindViewHolder(holder: GroupViewHolder, position: Int) {
-        getItem(position)?.let { holder.bind(it) }
+        holder.bind(getItem(position))
+    }
+
+    override fun onBindViewHolder(
+        holder: GroupViewHolder,
+        position: Int,
+        payloads: MutableList<Any>
+    ) {
+        if (payloads.isNullOrEmpty()) {
+            super.onBindViewHolder(holder, position, payloads)
+        } else {
+            payloads.forEach {
+                (it as? GroupListItemPayload)?.let { payload -> holder.bindPayload(payload) }
+            }
+        }
     }
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/groups/timeline/GroupTimelineFragment.kt b/app/src/main/java/com/futo/circles/ui/groups/timeline/GroupTimelineFragment.kt
index fa16b9618902bf788fef3f12268de069d135d201..b4092777cef57b97b76130c8350da88e1cf6a26d 100644
--- a/app/src/main/java/com/futo/circles/ui/groups/timeline/GroupTimelineFragment.kt
+++ b/app/src/main/java/com/futo/circles/ui/groups/timeline/GroupTimelineFragment.kt
@@ -6,21 +6,56 @@ import androidx.fragment.app.Fragment
 import androidx.navigation.fragment.navArgs
 import by.kirich1409.viewbindingdelegate.viewBinding
 import com.futo.circles.R
+import com.futo.circles.base.BaseRvDecoration
 import com.futo.circles.databinding.GroupTimelineFragmentBinding
+import com.futo.circles.extensions.bindToFab
+import com.futo.circles.extensions.dimen
 import com.futo.circles.extensions.observeData
 import com.futo.circles.extensions.setToolbarTitle
+import com.futo.circles.model.Post
+import com.futo.circles.ui.groups.timeline.list.GroupPostViewHolder
+import com.futo.circles.ui.groups.timeline.list.GroupTimelineAdapter
+import com.futo.circles.ui.view.GroupPostListener
 import org.koin.androidx.viewmodel.ext.android.viewModel
 import org.koin.core.parameter.parametersOf
 
-class GroupTimelineFragment : Fragment(R.layout.group_timeline_fragment) {
+class GroupTimelineFragment : Fragment(R.layout.group_timeline_fragment), GroupPostListener {
 
     private val args: GroupTimelineFragmentArgs by navArgs()
     private val viewModel by viewModel<GroupTimelineViewModel> { parametersOf(args.roomId) }
     private val binding by viewBinding(GroupTimelineFragmentBinding::bind)
 
+    private val listAdapter by lazy {
+        GroupTimelineAdapter(this, viewModel.urlResolver) { viewModel.loadMore() }
+    }
+
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        viewModel.titleLiveData.observeData(this) { title -> setToolbarTitle(title) }
+        binding.tvGroupTimeline.apply {
+            adapter = listAdapter
+            addItemDecoration(
+                BaseRvDecoration.OffsetDecoration<GroupPostViewHolder>(
+                    offset = context.dimen(R.dimen.group_post_item_offset)
+                )
+            )
+            bindToFab(binding.fbCreatePost)
+        }
+        setupObservers()
+    }
+
+    private fun setupObservers() {
+        with(viewModel) {
+            titleLiveData.observeData(this@GroupTimelineFragment) { title -> setToolbarTitle(title) }
+            timelineEventsLiveData.observeData(this@GroupTimelineFragment, ::setTimelineList)
+        }
+    }
+
+    private fun setTimelineList(list: List<Post>) {
+        listAdapter.submitList(list)
+    }
+
+    override fun onShowRepliesClicked(eventId: String) {
+        viewModel.toggleRepliesVisibilityFor(eventId)
     }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/groups/timeline/GroupTimelineViewModel.kt b/app/src/main/java/com/futo/circles/ui/groups/timeline/GroupTimelineViewModel.kt
index de15ed4849a462090e25a6830c51ce95073c8e6e..76142252a308820e2a9169f60787441eccd7c217 100644
--- a/app/src/main/java/com/futo/circles/ui/groups/timeline/GroupTimelineViewModel.kt
+++ b/app/src/main/java/com/futo/circles/ui/groups/timeline/GroupTimelineViewModel.kt
@@ -2,16 +2,31 @@ package com.futo.circles.ui.groups.timeline
 
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.ViewModel
-import com.futo.circles.extensions.nameOrId
-import com.futo.circles.provider.MatrixSessionProvider
+import com.futo.circles.ui.groups.timeline.data_source.GroupTimelineDatasource
 
 class GroupTimelineViewModel(
-    private val roomId: String,
-    private val matrixSessionProvider: MatrixSessionProvider
+    private val dataSource: GroupTimelineDatasource
 ) : ViewModel() {
 
-    val titleLiveData = MutableLiveData(getRoom()?.roomSummary()?.nameOrId() ?: roomId)
+    val titleLiveData = MutableLiveData(dataSource.getGroupTitle())
+    val timelineEventsLiveData = dataSource.timelineEventsLiveData
+    val urlResolver get() = dataSource.getUrlResolver()
 
-    private fun getRoom() = matrixSessionProvider.currentSession?.getRoom(roomId)
+    init {
+        dataSource.startTimeline()
+    }
+
+    fun loadMore() {
+        dataSource.loadMore()
+    }
+
+    fun toggleRepliesVisibilityFor(eventId: String) {
+        dataSource.toggleRepliesVisibility(eventId)
+    }
+
+    override fun onCleared() {
+        dataSource.clearTimeline()
+        super.onCleared()
+    }
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/groups/timeline/data_source/GroupTimelineBuilder.kt b/app/src/main/java/com/futo/circles/ui/groups/timeline/data_source/GroupTimelineBuilder.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0a51eb62da5acc095a207aadd14e13c2b9675581
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/ui/groups/timeline/data_source/GroupTimelineBuilder.kt
@@ -0,0 +1,105 @@
+package com.futo.circles.ui.groups.timeline.data_source
+
+import com.futo.circles.mapping.toPost
+import com.futo.circles.model.Post
+import com.futo.circles.model.PostContentType
+import com.futo.circles.model.ReplyPost
+import com.futo.circles.model.RootPost
+import org.matrix.android.sdk.api.session.events.model.EventType
+import org.matrix.android.sdk.api.session.events.model.toModel
+import org.matrix.android.sdk.api.session.room.model.message.MessageContent
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+
+class GroupTimelineBuilder {
+
+    private val repliesVisibleEvents: MutableSet<String> = mutableSetOf()
+
+    private var currentList: MutableList<Post> = mutableListOf()
+
+    fun build(list: List<TimelineEvent>): List<Post> {
+        val messageTimelineEvents = getOnlyMessageTimelineEvents(list)
+        val groupMessages = transformToGroupPosts(messageTimelineEvents)
+        val messagesWithReplies = setupRootMessagesWithVisibleReplies(groupMessages)
+        return toFlatPostsList(messagesWithReplies).also { currentList = it }
+    }
+
+    fun toggleRepliesVisibilityFor(eventId: String): List<Post> =
+        if (isRepliesVisibleFor(eventId)) removeRepliesFromCurrentListFor(eventId)
+        else addRepliesToCurrentListFor(eventId)
+
+    private fun addRepliesToCurrentListFor(eventId: String): List<Post> {
+        val list: MutableList<Post> = mutableListOf()
+        repliesVisibleEvents.add(eventId)
+        currentList.forEach { post ->
+            if (post.id == eventId && post is RootPost) {
+                list.add(post.copy(isRepliesVisible = true))
+                list.addAll(post.replies)
+            } else {
+                list.add(post)
+            }
+        }
+        return list.also { currentList = list }
+    }
+
+    private fun removeRepliesFromCurrentListFor(eventId: String): List<Post> {
+        val list: MutableList<Post> = mutableListOf()
+        repliesVisibleEvents.remove(eventId)
+        val rootPostsList = getOnlyRootMessages(currentList)
+        rootPostsList.forEach { rootPost ->
+            if (rootPost.id == eventId) {
+                list.add(rootPost.copy(isRepliesVisible = false))
+            } else {
+                list.add(rootPost)
+                if (rootPost.isRepliesVisible) list.addAll(rootPost.replies)
+            }
+        }
+        return list.also { currentList = list }
+    }
+
+    private fun toFlatPostsList(messagesWithReplies: List<RootPost>): MutableList<Post> {
+        val list: MutableList<Post> = mutableListOf()
+        messagesWithReplies.forEach { message ->
+            list.add(message)
+            if (message.isRepliesVisible) list.addAll(message.replies)
+        }
+        return list
+    }
+
+    private fun getOnlyMessageTimelineEvents(list: List<TimelineEvent>): List<TimelineEvent> =
+        list.filter { it.root.getClearType() == EventType.MESSAGE }
+
+    private fun isRepliesVisibleFor(id: String) = repliesVisibleEvents.contains(id)
+
+    private fun transformToGroupPosts(list: List<TimelineEvent>): List<Post> {
+        return list.mapNotNull { timelineEvent ->
+            getPostContentTypeFor(timelineEvent)?.let { contentType ->
+                timelineEvent.toPost(contentType, isRepliesVisibleFor(timelineEvent.eventId))
+            }
+        }
+    }
+
+    private fun getPostContentTypeFor(event: TimelineEvent): PostContentType? {
+        val messageType = event.root.getClearContent()?.toModel<MessageContent>()?.msgType
+        return PostContentType.values().firstOrNull { it.typeKey == messageType }
+    }
+
+    private fun setupRootMessagesWithVisibleReplies(groupMessages: List<Post>): List<RootPost> {
+        val rootMessages = getOnlyRootMessages(groupMessages)
+        val replies = getOnlyRepliesMessages(groupMessages)
+        val list = rootMessages.map { message ->
+            val repliesForEvent = getRepliesFor(replies, message.id)
+            message.copy(replies = repliesForEvent)
+        }
+        return list
+    }
+
+    private fun getOnlyRootMessages(list: List<Post>): List<RootPost> =
+        list.filterIsInstance<RootPost>()
+
+    private fun getOnlyRepliesMessages(list: List<Post>): List<ReplyPost> =
+        list.filterIsInstance<ReplyPost>()
+
+    private fun getRepliesFor(replies: List<ReplyPost>, eventId: String) =
+        replies.filter { eventId == it.replyToId }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/groups/timeline/data_source/GroupTimelineDatasource.kt b/app/src/main/java/com/futo/circles/ui/groups/timeline/data_source/GroupTimelineDatasource.kt
new file mode 100644
index 0000000000000000000000000000000000000000..1058d7f783f65223fe634d7a9665e8460d0643fc
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/ui/groups/timeline/data_source/GroupTimelineDatasource.kt
@@ -0,0 +1,62 @@
+package com.futo.circles.ui.groups.timeline.data_source
+
+import androidx.lifecycle.MutableLiveData
+import com.futo.circles.extensions.nameOrId
+import com.futo.circles.model.Post
+import com.futo.circles.provider.MatrixSessionProvider
+import org.matrix.android.sdk.api.session.room.timeline.Timeline
+import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
+import org.matrix.android.sdk.api.session.room.timeline.TimelineSettings
+
+class GroupTimelineDatasource(
+    private val roomId: String,
+    private val matrixSessionProvider: MatrixSessionProvider,
+    private val timelineBuilder: GroupTimelineBuilder
+) : Timeline.Listener {
+
+    private val room = matrixSessionProvider.currentSession?.getRoom(roomId)
+
+    val timelineEventsLiveData = MutableLiveData<List<Post>>()
+
+    private var timeline: Timeline? = null
+
+    fun getGroupTitle() = room?.roomSummary()?.nameOrId() ?: roomId
+
+    fun startTimeline() {
+        timeline = room?.createTimeline(null, TimelineSettings(MESSAGES_PER_PAGE))?.apply {
+            addListener(this@GroupTimelineDatasource)
+            start()
+        }
+    }
+
+    fun clearTimeline() {
+        timeline?.apply {
+            removeAllListeners()
+            dispose()
+        }
+    }
+
+    fun loadMore() {
+        if (timeline?.hasMoreToLoad(Timeline.Direction.BACKWARDS) == true)
+            timeline?.paginate(Timeline.Direction.BACKWARDS, MESSAGES_PER_PAGE)
+    }
+
+    fun toggleRepliesVisibility(eventId: String) {
+        timelineEventsLiveData.value = timelineBuilder.toggleRepliesVisibilityFor(eventId)
+    }
+
+    override fun onTimelineUpdated(snapshot: List<TimelineEvent>) {
+        timelineEventsLiveData.value = timelineBuilder.build(snapshot)
+    }
+
+    override fun onTimelineFailure(throwable: Throwable) {
+        timeline?.restartWithEventId(null)
+    }
+
+    fun getUrlResolver() = matrixSessionProvider.currentSession?.contentUrlResolver()
+
+    companion object {
+        private const val MESSAGES_PER_PAGE = 30
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/groups/timeline/list/GroupTimelineAdapter.kt b/app/src/main/java/com/futo/circles/ui/groups/timeline/list/GroupTimelineAdapter.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c9245dc13a3243ea9a5e40fe5b7203c462fe9011
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/ui/groups/timeline/list/GroupTimelineAdapter.kt
@@ -0,0 +1,61 @@
+package com.futo.circles.ui.groups.timeline.list
+
+import android.view.ViewGroup
+import com.futo.circles.base.BaseRvAdapter
+import com.futo.circles.model.Post
+import com.futo.circles.model.PostContentType
+import com.futo.circles.model.PostItemPayload
+import com.futo.circles.model.RootPost
+import com.futo.circles.ui.view.GroupPostListener
+import org.matrix.android.sdk.api.session.content.ContentUrlResolver
+
+class GroupTimelineAdapter(
+    private val postListener: GroupPostListener,
+    private val urlResolver: ContentUrlResolver?,
+    private val onLoadMore: () -> Unit
+) : BaseRvAdapter<Post, GroupPostViewHolder>(PayloadIdEntityCallback { _, new ->
+    (new as? RootPost)?.let { rootPost ->
+        PostItemPayload(
+            repliesCount = rootPost.getRepliesCount(),
+            isRepliesVisible = rootPost.isRepliesVisible,
+            hasReplies = rootPost.hasReplies()
+        )
+    }
+}) {
+
+
+    override fun getItemViewType(position: Int): Int {
+        return getItem(position).content.type.ordinal
+    }
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): GroupPostViewHolder {
+        return when (PostContentType.values()[viewType]) {
+            PostContentType.TEXT_CONTENT -> TextPostViewHolder(parent, postListener, urlResolver)
+            PostContentType.IMAGE_CONTENT -> ImagePostViewHolder(parent, postListener, urlResolver)
+        }
+    }
+
+    override fun onBindViewHolder(holder: GroupPostViewHolder, position: Int) {
+        holder.bind(getItem(position))
+        if (position >= itemCount - LOAD_MORE_THRESHOLD) onLoadMore()
+    }
+
+    override fun onBindViewHolder(
+        holder: GroupPostViewHolder,
+        position: Int,
+        payloads: MutableList<Any>
+    ) {
+        if (payloads.isNullOrEmpty()) {
+            super.onBindViewHolder(holder, position, payloads)
+        } else {
+            payloads.forEach {
+                (it as? PostItemPayload)?.let { payload -> holder.bindPayload(payload) }
+            }
+        }
+    }
+
+    companion object {
+        private const val LOAD_MORE_THRESHOLD = 10
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/groups/timeline/list/GroupTimelineViewHolder.kt b/app/src/main/java/com/futo/circles/ui/groups/timeline/list/GroupTimelineViewHolder.kt
new file mode 100644
index 0000000000000000000000000000000000000000..af5a82640a1a21d47ed55a8c4e4fb054ca4a9d33
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/ui/groups/timeline/list/GroupTimelineViewHolder.kt
@@ -0,0 +1,78 @@
+package com.futo.circles.ui.groups.timeline.list
+
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.futo.circles.base.ViewBindingHolder
+import com.futo.circles.databinding.ImagePostViewBinding
+import com.futo.circles.databinding.TextPostViewBinding
+import com.futo.circles.extensions.loadMatrixThumbnail
+import com.futo.circles.model.ImageContent
+import com.futo.circles.model.Post
+import com.futo.circles.model.PostItemPayload
+import com.futo.circles.model.TextContent
+import com.futo.circles.ui.view.GroupPostListener
+import com.futo.circles.ui.view.PostLayout
+import org.matrix.android.sdk.api.session.content.ContentUrlResolver
+
+sealed class GroupPostViewHolder(view: View, private val urlResolver: ContentUrlResolver?) :
+    RecyclerView.ViewHolder(view) {
+
+    abstract val postLayout: PostLayout
+
+    open fun bind(post: Post) {
+        postLayout.setData(post, urlResolver)
+    }
+
+    fun bindPayload(payload: PostItemPayload) {
+        postLayout.setPayload(payload)
+    }
+}
+
+class TextPostViewHolder(
+    parent: ViewGroup,
+    postListener: GroupPostListener,
+    private val urlResolver: ContentUrlResolver?
+) : GroupPostViewHolder(inflate(parent, TextPostViewBinding::inflate), urlResolver) {
+
+    private companion object : ViewBindingHolder
+
+    private val binding = baseBinding as TextPostViewBinding
+    override val postLayout: PostLayout = binding.lTextPost
+
+    init {
+        binding.lTextPost.setListener(postListener)
+    }
+
+    override fun bind(post: Post) {
+        super.bind(post)
+
+        (post.content as? TextContent)?.let {
+            binding.tvContent.text = it.message
+        }
+    }
+}
+
+class ImagePostViewHolder(
+    parent: ViewGroup,
+    postListener: GroupPostListener,
+    private val urlResolver: ContentUrlResolver?
+) : GroupPostViewHolder(inflate(parent, ImagePostViewBinding::inflate), urlResolver) {
+
+    private companion object : ViewBindingHolder
+
+    private val binding = baseBinding as ImagePostViewBinding
+    override val postLayout: PostLayout = binding.lImagePost
+
+    init {
+        binding.lImagePost.setListener(postListener)
+    }
+
+    override fun bind(post: Post) {
+        super.bind(post)
+
+        (post.content as? ImageContent)?.let {
+            binding.ivContent.loadMatrixThumbnail(it.url, urlResolver)
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/log_in/data_source/LoginDataSource.kt b/app/src/main/java/com/futo/circles/ui/log_in/data_source/LoginDataSource.kt
index 4602e672ae87ef4eb695ff42d7beea33d3e65ab6..2e809484ac6a17cab4b3c8bb803d1e88498ea703 100644
--- a/app/src/main/java/com/futo/circles/ui/log_in/data_source/LoginDataSource.kt
+++ b/app/src/main/java/com/futo/circles/ui/log_in/data_source/LoginDataSource.kt
@@ -1,23 +1,30 @@
 package com.futo.circles.ui.log_in.data_source
 
+import android.content.Context
+import com.futo.circles.R
 import com.futo.circles.extensions.createResult
 import com.futo.circles.provider.MatrixHomeServerProvider
-import com.futo.circles.provider.MatrixProvider
 import com.futo.circles.provider.MatrixSessionProvider
-import java.util.*
+import org.matrix.android.sdk.api.Matrix
 
-class LoginDataSource(private val matrixSessionProvider: MatrixSessionProvider) {
+class LoginDataSource(
+    private val context: Context,
+    private val matrixSessionProvider: MatrixSessionProvider
+) {
 
     suspend fun logIn(name: String, password: String, secondPassword: String?) =
         createResult {
             val homeServerConnectionConfig = MatrixHomeServerProvider().createHomeServerConfig()
 
-            MatrixProvider.matrix.authenticationService().directAuthentication(
+            Matrix.getInstance(context).authenticationService().directAuthentication(
                 homeServerConnectionConfig = homeServerConnectionConfig,
                 matrixId = name,
                 password = password,
                 deviceId = secondPassword,
-                initialDeviceName = UUID.randomUUID().toString()
+                initialDeviceName = context.getString(
+                    R.string.initial_device_name,
+                    context.getString(R.string.app_name)
+                )
             ).also { matrixSessionProvider.startSession(it) }
         }
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/view/AdvancedOptionsView.kt b/app/src/main/java/com/futo/circles/ui/view/AdvancedOptionsView.kt
index 791bcf3874f7e1af0284a4ed5a5a506f4a3019a7..b4a5fa6f3a876be2b20e3b3071a8b957527bd1a8 100644
--- a/app/src/main/java/com/futo/circles/ui/view/AdvancedOptionsView.kt
+++ b/app/src/main/java/com/futo/circles/ui/view/AdvancedOptionsView.kt
@@ -8,6 +8,7 @@ import androidx.core.view.isVisible
 import com.futo.circles.R
 import com.futo.circles.databinding.AdvancedOptionsViewBinding
 import com.futo.circles.extensions.gone
+import com.futo.circles.extensions.setVisibility
 import com.futo.circles.extensions.visible
 
 class AdvancedOptionsView(
@@ -28,19 +29,9 @@ class AdvancedOptionsView(
         binding.tilPassword.editText?.text.toString().takeIf { it.isNotEmpty() }
 
     private fun toggleEncryptionPasswordVisibility() {
-        if (binding.tilPassword.isVisible) {
-            binding.tilPassword.gone()
-            binding.btnAdvanced.apply {
-                text = context.getString(R.string.advanced_options)
-                setIconResource(R.drawable.ic_keyboard_arrow_right)
-            }
-        } else {
-            binding.tilPassword.visible()
-            binding.btnAdvanced.apply {
-                text = context.getString(R.string.hide_advanced_options)
-                setIconResource(R.drawable.ic_keyboard_arrow_down)
-            }
-        }
+        val isOpened = binding.btnAdvanced.isOpened()
+        binding.tilPassword.setVisibility(!isOpened)
+        binding.btnAdvanced.setIsOpened(!isOpened)
     }
 
 }
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/view/ExpandContentButton.kt b/app/src/main/java/com/futo/circles/ui/view/ExpandContentButton.kt
new file mode 100644
index 0000000000000000000000000000000000000000..0e126b9e3cef5a8e55d3640611395f6e62f804a6
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/ui/view/ExpandContentButton.kt
@@ -0,0 +1,65 @@
+package com.futo.circles.ui.view
+
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.util.AttributeSet
+import com.futo.circles.R
+import com.futo.circles.extensions.getAttributes
+import com.google.android.material.button.MaterialButton
+
+class ExpandContentButton(
+    context: Context,
+    attrs: AttributeSet? = null,
+) : MaterialButton(context, attrs) {
+
+    private var openedText: String = ""
+    private var closedText: String = ""
+
+    private var openedIcon: Drawable? = null
+    private var closedIcon: Drawable? = null
+
+    private var isOpened: Boolean = false
+
+    init {
+        getAttributes(attrs, R.styleable.ExpandContentButton) {
+            getText(R.styleable.ExpandContentButton_closed_text)?.let {
+                text = it
+                closedText = it.toString()
+            }
+            openedText = getText(R.styleable.ExpandContentButton_opened_text)?.toString() ?: ""
+
+            getDrawable(R.styleable.ExpandContentButton_closed_icon)?.let {
+                icon = it
+                closedIcon = it
+            }
+
+            getDrawable(R.styleable.ExpandContentButton_opened_icon)?.let {
+                openedIcon = it
+            }
+        }
+    }
+
+    fun setClosedText(title: String) {
+        text = title.also { closedText = it }
+    }
+
+    fun setIsOpened(isOpened: Boolean) {
+        if (isOpened) open()
+        else close()
+    }
+
+    fun isOpened() = isOpened
+
+    private fun open() {
+        isOpened = true
+        text = openedText
+        icon = openedIcon
+    }
+
+    private fun close() {
+        isOpened = false
+        text = closedText
+        icon = closedIcon
+    }
+}
+
diff --git a/app/src/main/java/com/futo/circles/ui/view/GroupPostFooterView.kt b/app/src/main/java/com/futo/circles/ui/view/GroupPostFooterView.kt
new file mode 100644
index 0000000000000000000000000000000000000000..71d4e9e399bcc16af638cbf4cb4eb946435bc60f
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/ui/view/GroupPostFooterView.kt
@@ -0,0 +1,31 @@
+package com.futo.circles.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.futo.circles.databinding.GroupPostFooterViewBinding
+import com.futo.circles.extensions.setIsEncryptedIcon
+import com.futo.circles.extensions.setVisibility
+import com.futo.circles.model.PostInfo
+import java.text.DateFormat
+import java.util.*
+
+
+class GroupPostFooterView(
+    context: Context,
+    attrs: AttributeSet? = null,
+) : ConstraintLayout(context, attrs) {
+
+    private val binding =
+        GroupPostFooterViewBinding.inflate(LayoutInflater.from(context), this)
+
+    fun setData(data: PostInfo, isReply:Boolean) {
+        with(binding){
+            btnReply.setVisibility(!isReply)
+            ivEncrypted.setIsEncryptedIcon(data.isEncrypted)
+            tvMessageTime.text = DateFormat.getDateTimeInstance().format(Date(data.timestamp))
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/view/GroupPostHeaderView.kt b/app/src/main/java/com/futo/circles/ui/view/GroupPostHeaderView.kt
new file mode 100644
index 0000000000000000000000000000000000000000..66155df99890c55c6ceb477ffda2264241b629cc
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/ui/view/GroupPostHeaderView.kt
@@ -0,0 +1,30 @@
+package com.futo.circles.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.futo.circles.databinding.GroupPostHeaderViewBinding
+import com.futo.circles.extensions.loadMatrixThumbnail
+import org.matrix.android.sdk.api.session.content.ContentUrlResolver
+import org.matrix.android.sdk.api.session.room.sender.SenderInfo
+
+class GroupPostHeaderView(
+    context: Context,
+    attrs: AttributeSet? = null,
+) : ConstraintLayout(context, attrs) {
+
+    private val binding =
+        GroupPostHeaderViewBinding.inflate(LayoutInflater.from(context), this)
+
+    fun setData(sender: SenderInfo, urlResolver: ContentUrlResolver?) {
+        binding.ivSenderImage.loadMatrixThumbnail(
+            sender.avatarUrl,
+            urlResolver,
+            binding.ivSenderImage.height
+        )
+        binding.tvUserName.text = sender.disambiguatedDisplayName
+        binding.tvUserId.text = sender.userId
+    }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/futo/circles/ui/view/LoadingButton.kt b/app/src/main/java/com/futo/circles/ui/view/LoadingButton.kt
index f4d761e427b977730dc19ae107799280a9dd8337..14bb59eb126bd93222914322e27f65957f85969c 100644
--- a/app/src/main/java/com/futo/circles/ui/view/LoadingButton.kt
+++ b/app/src/main/java/com/futo/circles/ui/view/LoadingButton.kt
@@ -21,8 +21,9 @@ class LoadingButton(
 
     init {
         getAttributes(attrs, R.styleable.LoadingButton) {
-            getResourceId(R.styleable.LoadingButton_android_text, 0).takeIf { it != 0 }?.let {
-                buttonText = context.getString(it).also { binding.button.text = it }
+            getText(R.styleable.LoadingButton_android_text)?.let {
+                buttonText = it.toString()
+                binding.button.text = it
             }
         }
     }
diff --git a/app/src/main/java/com/futo/circles/ui/view/PostLayout.kt b/app/src/main/java/com/futo/circles/ui/view/PostLayout.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2b8151253ba2c78253c71ad38902797e52e96bcc
--- /dev/null
+++ b/app/src/main/java/com/futo/circles/ui/view/PostLayout.kt
@@ -0,0 +1,99 @@
+package com.futo.circles.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.futo.circles.R
+import com.futo.circles.databinding.PostLayoutBinding
+import com.futo.circles.extensions.gone
+import com.futo.circles.extensions.setVisibility
+import com.futo.circles.model.Post
+import com.futo.circles.model.PostItemPayload
+import com.futo.circles.model.ReplyPost
+import com.futo.circles.model.RootPost
+import org.matrix.android.sdk.api.session.content.ContentUrlResolver
+
+
+interface GroupPostListener {
+    fun onShowRepliesClicked(eventId: String)
+}
+
+class PostLayout(
+    context: Context,
+    attrs: AttributeSet? = null,
+) : ConstraintLayout(context, attrs) {
+
+    private val binding =
+        PostLayoutBinding.inflate(LayoutInflater.from(context), this)
+
+    private var listener: GroupPostListener? = null
+    private var post: Post? = null
+
+    init {
+        binding.btnShowReplies.setOnClickListener {
+            post?.let { listener?.onShowRepliesClicked(it.id) }
+        }
+    }
+
+    fun setListener(groupPostListener: GroupPostListener) {
+        listener = groupPostListener
+    }
+
+
+    fun setData(data: Post, urlResolver: ContentUrlResolver?) {
+        post = data
+        setGeneralMessageData(data, urlResolver)
+        bindRepliesButton(data)
+    }
+
+    fun setPayload(payload: PostItemPayload) {
+        bindRepliesButton(payload.hasReplies, payload.repliesCount, payload.isRepliesVisible)
+    }
+
+    private fun setGeneralMessageData(
+        data: Post,
+        urlResolver: ContentUrlResolver?
+    ) {
+        val isReply = data is ReplyPost
+        binding.vReplyMargin.setVisibility(isReply)
+        binding.postHeader.setData(data.postInfo.sender, urlResolver)
+        binding.postFooter.setData(data.postInfo, isReply)
+    }
+
+    private fun bindRepliesButton(post: Post) {
+        val rootPost = (post as? RootPost) ?: kotlin.run { binding.btnShowReplies.gone(); return }
+
+        bindRepliesButton(
+            rootPost.hasReplies(), rootPost.getRepliesCount(), rootPost.isRepliesVisible
+        )
+    }
+
+    private fun bindRepliesButton(
+        hasReplies: Boolean,
+        repliesCount: Int,
+        isRepliesVisible: Boolean
+    ) {
+        with(binding.btnShowReplies) {
+            setVisibility(hasReplies)
+            setClosedText(
+                context.resources.getQuantityString(
+                    R.plurals.show__replies_plurals,
+                    repliesCount, repliesCount
+                )
+            )
+            setIsOpened(isRepliesVisible)
+        }
+    }
+
+    override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams?) {
+        if (child.id == R.id.postCard || child.id == R.id.btnShowReplies || child.id == R.id.vReplyMargin) {
+            super.addView(child, index, params)
+        } else {
+            findViewById<FrameLayout>(R.id.lvContent).addView(child, index, params)
+        }
+    }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_create.xml b/app/src/main/res/drawable/ic_create.xml
new file mode 100644
index 0000000000000000000000000000000000000000..faddfce421038a77e3a7e6658df78cdae0e1848f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_create.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#FFFFFF"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_like.xml b/app/src/main/res/drawable/ic_like.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2d9d13cfbf28628607689fa8f62ec7ef3fc4b2c5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_like.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#FFFFFF"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8bb3fc306f2440057586e0f419521985059b4186
--- /dev/null
+++ b/app/src/main/res/drawable/ic_more.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#FFFFFF"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_reply.xml b/app/src/main/res/drawable/ic_reply.xml
new file mode 100644
index 0000000000000000000000000000000000000000..f79c9c8ef6610ccbe731db4d28b101bfbed344a2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_reply.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#FFFFFF"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M20,2L4,2c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM20,16L6,16l-2,2L4,4h16v12z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d0d075a1f76d90fc6f7728d3a26a0f4bdb62badb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_share.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#FFFFFF"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92s2.92,-1.31 2.92,-2.92c0,-1.61 -1.31,-2.92 -2.92,-2.92zM18,4c0.55,0 1,0.45 1,1s-0.45,1 -1,1 -1,-0.45 -1,-1 0.45,-1 1,-1zM6,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,20.02c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_unlike.xml b/app/src/main/res/drawable/ic_unlike.xml
new file mode 100644
index 0000000000000000000000000000000000000000..996e16d0f381e05453e40db5bc53f391c1b5b63e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_unlike.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#FFFFFF"
+    android:viewportHeight="24" android:viewportWidth="24"
+    android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+    <path android:fillColor="@android:color/white" android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z"/>
+</vector>
diff --git a/app/src/main/res/layout/advanced_options_view.xml b/app/src/main/res/layout/advanced_options_view.xml
index 4b910a8d8462c3e1d80ef14aac9f83a5375a9b11..f3870d3292f204786e92d3a3afa8ba9f815ddf49 100644
--- a/app/src/main/res/layout/advanced_options_view.xml
+++ b/app/src/main/res/layout/advanced_options_view.xml
@@ -6,17 +6,21 @@
     android:layout_height="wrap_content"
     tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
 
-    <com.google.android.material.button.MaterialButton
+    <com.futo.circles.ui.view.ExpandContentButton
         android:id="@+id/btnAdvanced"
         style="@style/Widget.MaterialComponents.Button.TextButton.Icon"
-        android:layout_width="0dp"
+        android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:minHeight="0dp"
         android:paddingStart="0dp"
         android:paddingEnd="40dp"
-        android:text="@string/advanced_options"
         android:textAppearance="@style/footNote"
-        app:icon="@drawable/ic_keyboard_arrow_right"
+        app:closed_icon="@drawable/ic_keyboard_arrow_right"
+        app:closed_text="@string/advanced_options"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:opened_icon="@drawable/ic_keyboard_arrow_down"
+        app:opened_text="@string/hide_advanced_options"
         tools:ignore="RtlSymmetry" />
 
     <com.google.android.material.textfield.TextInputLayout
diff --git a/app/src/main/res/layout/bottom_navigation_fragment.xml b/app/src/main/res/layout/bottom_navigation_fragment.xml
index 6d1f9e5a8cc62e1b367bcf093cf70526d7b649e9..73c37dab729b2586c3986a95e277b348f6246073 100644
--- a/app/src/main/res/layout/bottom_navigation_fragment.xml
+++ b/app/src/main/res/layout/bottom_navigation_fragment.xml
@@ -48,6 +48,7 @@
         android:id="@+id/bottomNavigationView"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
+        app:itemBackground="?selectableItemBackgroundBorderless"
         app:labelVisibilityMode="labeled"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintLeft_toLeftOf="parent"
diff --git a/app/src/main/res/layout/group_post_footer_view.xml b/app/src/main/res/layout/group_post_footer_view.xml
new file mode 100644
index 0000000000000000000000000000000000000000..92959590f3a07a9a6b1358d9e036bf25096a7df3
--- /dev/null
+++ b/app/src/main/res/layout/group_post_footer_view.xml
@@ -0,0 +1,79 @@
+<?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">
+
+    <ImageView
+        android:id="@+id/ivEncrypted"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="@id/tvMessageTime"
+        app:layout_constraintDimensionRatio="h,1:1"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="@id/tvMessageTime"
+        tools:src="@drawable/ic_lock" />
+
+    <TextView
+        android:id="@+id/tvMessageTime"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:ellipsize="end"
+        android:lines="1"
+        android:textSize="12sp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/ivEncrypted"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:text="some date" />
+
+    <View
+        android:id="@+id/divider"
+        android:layout_width="0dp"
+        android:layout_height="@dimen/divider_height"
+        android:layout_marginTop="8dp"
+        android:background="@color/divider_color"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/tvMessageTime" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btnLike"
+        style="@style/PostButtonStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/like"
+        app:icon="@drawable/ic_unlike"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/btnReply"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/divider" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btnReply"
+        style="@style/PostButtonStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/reply"
+        app:icon="@drawable/ic_reply"
+        app:layout_constraintBottom_toBottomOf="@id/btnLike"
+        app:layout_constraintEnd_toStartOf="@id/btnShare"
+        app:layout_constraintStart_toEndOf="@id/btnLike"
+        app:layout_constraintTop_toTopOf="@id/btnLike" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/btnShare"
+        style="@style/PostButtonStyle"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/share"
+        app:icon="@drawable/ic_share"
+        app:layout_constraintBottom_toBottomOf="@id/btnLike"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/btnReply"
+        app:layout_constraintTop_toTopOf="@id/btnLike" />
+
+
+</merge>
\ No newline at end of file
diff --git a/app/src/main/res/layout/group_post_header_view.xml b/app/src/main/res/layout/group_post_header_view.xml
new file mode 100644
index 0000000000000000000000000000000000000000..2245cd65a7c7c1ed2b59856c6f744e30541cb756
--- /dev/null
+++ b/app/src/main/res/layout/group_post_header_view.xml
@@ -0,0 +1,60 @@
+<?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">
+
+    <com.google.android.material.imageview.ShapeableImageView
+        android:id="@+id/ivSenderImage"
+        android:layout_width="40dp"
+        android:layout_height="0dp"
+        app:layout_constraintDimensionRatio="w,1:1"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.CornerSize50Percent"
+        tools:background="@color/blue" />
+
+    <TextView
+        android:id="@+id/tvUserName"
+        style="@style/body"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="8dp"
+        android:layout_marginEnd="8dp"
+        android:ellipsize="end"
+        android:lines="1"
+        app:layout_constraintBottom_toTopOf="@id/tvUserId"
+        app:layout_constraintEnd_toStartOf="@id/btnMore"
+        app:layout_constraintStart_toEndOf="@id/ivSenderImage"
+        app:layout_constraintTop_toTopOf="@id/ivSenderImage"
+        app:layout_constraintVertical_chainStyle="packed"
+        tools:text="Android01" />
+
+    <TextView
+        android:id="@+id/tvUserId"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:ellipsize="end"
+        android:lines="1"
+        android:textSize="13sp"
+        app:layout_constraintBottom_toBottomOf="@id/ivSenderImage"
+        app:layout_constraintEnd_toEndOf="@id/tvUserName"
+        app:layout_constraintStart_toStartOf="@id/tvUserName"
+        app:layout_constraintTop_toBottomOf="@id/tvUserName"
+        tools:text="Android01@domain" />
+
+    <ImageButton
+        android:id="@+id/btnMore"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="?selectableItemBackgroundBorderless"
+        android:paddingHorizontal="4dp"
+        android:paddingBottom="2dp"
+        android:src="@drawable/ic_more"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:tint="@color/blue" />
+
+</merge>
\ No newline at end of file
diff --git a/app/src/main/res/layout/group_timeline_fragment.xml b/app/src/main/res/layout/group_timeline_fragment.xml
index 77d9ef65f8c7c6bf54bdef7d54d1646b86417404..5873afd039dcc9ebdd03989cbf5efda09b0fd34d 100644
--- a/app/src/main/res/layout/group_timeline_fragment.xml
+++ b/app/src/main/res/layout/group_timeline_fragment.xml
@@ -1,6 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<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">
 
-</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/tvGroupTimeline"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipToPadding="false"
+        android:clipChildren="false"
+        app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
+
+
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/fbCreatePost"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_margin="16dp"
+        app:fabSize="normal"
+        app:layout_anchor="@id/tvGroupTimeline"
+        app:srcCompat="@drawable/ic_create"
+        app:tint="@color/white"
+        tools:ignore="ContentDescription" />
+</FrameLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/image_post_view.xml b/app/src/main/res/layout/image_post_view.xml
new file mode 100644
index 0000000000000000000000000000000000000000..460a022b54cc3cae37bca66c625f8d0adc73502e
--- /dev/null
+++ b/app/src/main/res/layout/image_post_view.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.futo.circles.ui.view.PostLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/lImagePost"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <ImageView
+        android:id="@+id/ivContent"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content" />
+
+</com.futo.circles.ui.view.PostLayout>
\ No newline at end of file
diff --git a/app/src/main/res/layout/post_layout.xml b/app/src/main/res/layout/post_layout.xml
new file mode 100644
index 0000000000000000000000000000000000000000..74f47e39c5a97792baa25d0e1137fa01003d08fc
--- /dev/null
+++ b/app/src/main/res/layout/post_layout.xml
@@ -0,0 +1,105 @@
+<?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"
+    android:orientation="horizontal"
+    tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
+
+    <View
+        android:id="@+id/vReplyMargin"
+        android:layout_width="@dimen/reply_post_item_margin"
+        android:layout_height="0dp"
+        android:visibility="gone"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+
+    <androidx.cardview.widget.CardView
+        android:id="@+id/postCard"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        app:cardCornerRadius="4dp"
+        app:cardElevation="8dp"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toEndOf="@id/vReplyMargin"
+        app:layout_constraintTop_toTopOf="parent">
+
+        <androidx.constraintlayout.widget.ConstraintLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <androidx.constraintlayout.widget.Guideline
+                android:id="@+id/guidelineStart"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                app:layout_constraintGuide_begin="8dp" />
+
+            <androidx.constraintlayout.widget.Guideline
+                android:id="@+id/guidelineEnd"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical"
+                app:layout_constraintGuide_end="8dp" />
+
+            <androidx.constraintlayout.widget.Guideline
+                android:id="@+id/guidelineTop"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="horizontal"
+                app:layout_constraintGuide_begin="8dp" />
+
+            <com.futo.circles.ui.view.GroupPostHeaderView
+                android:id="@+id/postHeader"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
+                app:layout_constraintStart_toStartOf="@id/guidelineStart"
+                app:layout_constraintTop_toTopOf="@id/guidelineTop" />
+
+
+            <FrameLayout
+                android:id="@+id/lvContent"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="16dp"
+                app:layout_constraintEnd_toEndOf="parent"
+                app:layout_constraintStart_toStartOf="parent"
+                app:layout_constraintTop_toBottomOf="@id/postHeader" />
+
+
+            <com.futo.circles.ui.view.GroupPostFooterView
+                android:id="@+id/postFooter"
+                android:layout_width="0dp"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="16dp"
+                app:layout_constraintBottom_toBottomOf="parent"
+                app:layout_constraintEnd_toEndOf="@id/guidelineEnd"
+                app:layout_constraintStart_toStartOf="@id/guidelineStart"
+                app:layout_constraintTop_toBottomOf="@id/lvContent" />
+
+        </androidx.constraintlayout.widget.ConstraintLayout>
+
+    </androidx.cardview.widget.CardView>
+
+    <com.futo.circles.ui.view.ExpandContentButton
+        android:id="@+id/btnShowReplies"
+        style="@style/Widget.MaterialComponents.Button.TextButton.Icon"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="8dp"
+        android:minHeight="0dp"
+        android:textAppearance="@style/footNote"
+        android:visibility="gone"
+        app:closed_icon="@drawable/ic_keyboard_arrow_right"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/postCard"
+        app:opened_icon="@drawable/ic_keyboard_arrow_down"
+        app:opened_text="@string/hide_replies"
+        tools:ignore="RtlSymmetry"
+        tools:visibility="visible" />
+
+</merge>
\ No newline at end of file
diff --git a/app/src/main/res/layout/text_post_view.xml b/app/src/main/res/layout/text_post_view.xml
new file mode 100644
index 0000000000000000000000000000000000000000..c11aade96b932a5d1d33e47e7e8a803f752f046a
--- /dev/null
+++ b/app/src/main/res/layout/text_post_view.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.futo.circles.ui.view.PostLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/lTextPost"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <TextView
+        android:id="@+id/tvContent"
+        style="@style/body"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginHorizontal="@dimen/post_text_side_margin" />
+
+</com.futo.circles.ui.view.PostLayout>
\ No newline at end of file
diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml
index 480b4595c170199009c22c6132f7f6f21ed147cb..eee4ca85557fae5864f9e9ffaaafeac8836cf665 100644
--- a/app/src/main/res/values/attrs.xml
+++ b/app/src/main/res/values/attrs.xml
@@ -5,4 +5,11 @@
         <attr name="android:text" />
     </declare-styleable>
 
+    <declare-styleable name="ExpandContentButton">
+        <attr name="closed_text" format="string" />
+        <attr name="opened_text" format="string" />
+        <attr name="closed_icon" format="reference" />
+        <attr name="opened_icon" format="reference" />
+    </declare-styleable>
+
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index aa48f84f6246b1f70549b5c7d2a3f5d70488aef4..1c5fa8ee8a31bf1a0fe00d36e39464b1aedeaeb2 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -4,7 +4,6 @@
 
     <color name="purple_500">#FF6200EE</color>
     <color name="purple_700">#FF3700B3</color>
-    <color name="teal_200">#FF03DAC5</color>
     <color name="teal_700">#FF018786</color>
 
     <color name="black">#FF000000</color>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index e6d1263553903a59150ae19519a5b8552d8d880c..fa720bdd17e56bd896cb64a24603d3b1fec935b4 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,4 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
     <dimen name="divider_height">1dp</dimen>
+    <dimen name="group_post_item_offset">4dp</dimen>
+    <dimen name="reply_post_item_margin">24dp</dimen>
+    <dimen name="post_text_side_margin">24dp</dimen>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b13859d89244b3b1c4853b46f44fd7871eeebe75..6e2137b5289527d5e89a7729edfba4c60d3f2a08 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -21,9 +21,19 @@
     <string name="my_people">My People</string>
     <string name="my_circles">My Circles</string>
     <string name="photo_galleries">Photo Galleries</string>
+    <string name="initial_device_name">%s (Android)</string>
+    <string name="like">Like</string>
+    <string name="reply">Reply</string>
+    <string name="share">Share</string>
+    <string name="hide_replies">Hide replies</string>
 
     <plurals name="member_plurals">
         <item quantity="one">%d member</item>
         <item quantity="other">%d members</item>
     </plurals>
+
+    <plurals name="show__replies_plurals">
+        <item quantity="one">Show %d reply</item>
+        <item quantity="other">Show %d replies</item>
+    </plurals>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index f2feacee049c94f0227eb8099d580bc5b872e5d7..fdc19c79587083be35a5ba8ab360bdf6094f5eac 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -8,6 +8,16 @@
         <item name="android:textSize">17sp</item>
     </style>
 
+    <style name="PostButtonStyle" parent="@style/Widget.MaterialComponents.Button.TextButton.Icon">
+        <item name="textAllCaps">false</item>
+        <item name="cornerRadius">8dp</item>
+        <item name="android:textSize">15sp</item>
+        <item name="iconPadding">4dp</item>
+        <item name="iconSize">24dp</item>
+        <item name="android:padding">4dp</item>
+        <item name="android:minHeight">0dp</item>
+    </style>
+
     <style name="headline" parent="TextAppearance.MaterialComponents.Headline6">
         <item name="android:textStyle">bold</item>
         <item name="android:textSize">17sp</item>
@@ -38,4 +48,8 @@
         <item name="android:textColor">@color/gray</item>
         <item name="android:textSize">15sp</item>
     </style>
+
+    <style name="ShapeAppearanceOverlay.App.CornerSize50Percent" parent="">
+        <item name="cornerSize">50%</item>
+    </style>
 </resources>
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index b21c15d523bb740c344362cd7d1f50011f35687d..d7bf13054ddff5ce94281bea2d6eb72d00cf9041 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -6,10 +6,11 @@
         <item name="colorPrimaryVariant">@color/purple_700</item>
         <item name="colorOnPrimary">@color/white</item>
         <!-- Secondary brand color. -->
-        <item name="colorSecondary">@color/teal_200</item>
+        <item name="colorSecondary">@color/blue</item>
         <item name="colorSecondaryVariant">@color/teal_700</item>
         <item name="colorOnSecondary">@color/black</item>
 
+        <item name="colorControlHighlight">#330E7AFE</item>
         <item name="android:statusBarColor">@color/status_bar_color</item>
     </style>