diff --git a/core/src/main/java/org/futo/circles/core/feature/user/UserDialogFragment.kt b/core/src/main/java/org/futo/circles/core/feature/user/UserDialogFragment.kt
index 5530503eb6bd8c9f4bd859985de5f9981cb8ba42..b35cd9e37fcdf3f79494dfe7b5f4955457cd93bf 100644
--- a/core/src/main/java/org/futo/circles/core/feature/user/UserDialogFragment.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/user/UserDialogFragment.kt
@@ -120,7 +120,7 @@ class UserDialogFragment : BaseFullscreenDialogFragment(DialogFragmentUserBindin
             }
         }
         viewModel.userLiveData.observeData(this) { setupUserInfo(it) }
-        viewModel.timelineLiveDataLiveData.observeData(this) {
+        viewModel.usersTimelinesLiveData.observeData(this) {
             usersCirclesAdapter.submitList(it)
         }
         viewModel.requestFollowLiveData.observeResponse(this,
diff --git a/core/src/main/java/org/futo/circles/core/feature/user/UserViewModel.kt b/core/src/main/java/org/futo/circles/core/feature/user/UserViewModel.kt
index 36ecdbea7bbeea38eb1eecd1c34e1f7bfd8c142c..e3ba56c33f0e85e86032d617521f26de5d0f3f56 100644
--- a/core/src/main/java/org/futo/circles/core/feature/user/UserViewModel.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/user/UserViewModel.kt
@@ -1,5 +1,6 @@
 package org.futo.circles.core.feature.user
 
+import androidx.lifecycle.MediatorLiveData
 import androidx.lifecycle.MutableLiveData
 import androidx.lifecycle.SavedStateHandle
 import androidx.lifecycle.ViewModel
@@ -15,7 +16,9 @@ import org.futo.circles.core.extensions.launchUi
 import org.futo.circles.core.feature.room.RoomRelationsBuilder
 import org.futo.circles.core.feature.room.invite.ManageInviteRequestsDataSource
 import org.futo.circles.core.feature.workspace.SharedCircleDataSource
+import org.futo.circles.core.model.TimelineHeaderItem
 import org.futo.circles.core.model.TimelineListItem
+import org.futo.circles.core.model.TimelineRoomListItem
 import org.futo.circles.core.provider.MatrixSessionProvider
 import org.matrix.android.sdk.api.session.getRoom
 import javax.inject.Inject
@@ -34,7 +37,7 @@ class UserViewModel @Inject constructor(
     private val profileRoomId = sharedCircleDataSource.getSharedCirclesSpaceId() ?: ""
 
     val userLiveData = userDataSource.userLiveData
-    val timelineLiveDataLiveData = MutableLiveData<List<TimelineListItem>>()
+
     val requestFollowLiveData = SingleEventLiveData<Response<Unit?>>()
     val inviteToConnectLiveData = SingleEventLiveData<Response<Unit?>>()
     val ignoreUserLiveData = SingleEventLiveData<Response<Unit?>>()
@@ -44,6 +47,29 @@ class UserViewModel @Inject constructor(
         it.firstOrNull { it.userId == userId } != null
     }
 
+    private val timelineLiveData = MutableLiveData<List<TimelineListItem>>()
+    private val loadingItemsIdsList = MutableLiveData<Set<String>>(emptySet())
+
+    val usersTimelinesLiveData = MediatorLiveData<List<TimelineListItem>>().also {
+        it.addSource(loadingItemsIdsList) { loadingItemsValue ->
+            val currentList = it.value ?: emptyList()
+            it.postValue(
+                currentList.map { item ->
+                    when (item) {
+                        is TimelineRoomListItem -> item.copy(
+                            isLoading = loadingItemsValue.contains(item.id)
+                        )
+
+                        is TimelineHeaderItem -> item
+                    }
+                }
+            )
+        }
+        it.addSource(timelineLiveData) { value ->
+            it.postValue(value)
+        }
+    }
+
     init {
         getUsersTimelines()
     }
@@ -51,16 +77,18 @@ class UserViewModel @Inject constructor(
     private fun getUsersTimelines() {
         launchUi {
             userDataSource.getTimelinesFlow().collectLatest {
-                timelineLiveDataLiveData.postValue(it)
+                timelineLiveData.postValue(it)
             }
         }
     }
 
     fun requestFollowTimeline(timelineId: String) {
         launchBg {
+            toggleItemLoading(timelineId)
             val result = createResult {
                 MatrixSessionProvider.currentSession?.roomService()?.knock(timelineId)
             }
+            toggleItemLoading(timelineId)
             requestFollowLiveData.postValue(result)
         }
     }
@@ -68,8 +96,10 @@ class UserViewModel @Inject constructor(
     fun unFollowTimeline(timelineId: String) {
         launchBg {
             createResult {
+                toggleItemLoading(timelineId)
                 roomRelationsBuilder.removeFromAllParents(timelineId)
                 MatrixSessionProvider.currentSession?.roomService()?.leaveRoom(timelineId)
+                toggleItemLoading(timelineId)
             }
         }
     }
@@ -109,4 +139,13 @@ class UserViewModel @Inject constructor(
         }
     }
 
+    private fun toggleItemLoading(id: String) {
+        val currentSet = loadingItemsIdsList.value?.toMutableSet() ?: return
+        val newLoadingSet = currentSet.apply {
+            if (this.contains(id)) remove(id)
+            else add(id)
+        }
+        loadingItemsIdsList.postValue(newLoadingSet)
+    }
+
 }
\ No newline at end of file
diff --git a/core/src/main/java/org/futo/circles/core/feature/user/list/UsersCircleViewHolder.kt b/core/src/main/java/org/futo/circles/core/feature/user/list/UsersCircleViewHolder.kt
index a9158a2cb0260d351dd8f438e7947768c8b0fb1e..543077234d857f84103f421021047c3dfd41320b 100644
--- a/core/src/main/java/org/futo/circles/core/feature/user/list/UsersCircleViewHolder.kt
+++ b/core/src/main/java/org/futo/circles/core/feature/user/list/UsersCircleViewHolder.kt
@@ -39,8 +39,9 @@ class UsersTimelineRoomViewHolder(
         with(binding) {
             tvTimelineName.text = data.info.title
             ivTimelineImage.loadRoomProfileIcon(data.info.avatarUrl, data.info.title)
-            btnFollow.setIsVisible(!data.isJoined)
-            btnUnFollow.setIsVisible(data.isJoined)
+            vLoading.setIsVisible(data.isLoading)
+            btnFollow.setIsVisible(!data.isJoined && !data.isLoading)
+            btnUnFollow.setIsVisible(data.isJoined && !data.isLoading)
         }
     }
 }
diff --git a/core/src/main/java/org/futo/circles/core/model/TimelineListItem.kt b/core/src/main/java/org/futo/circles/core/model/TimelineListItem.kt
index 8c14a18deab25b56bea9d285a3bba4bfd458376f..54368d5782e641fd077e984d3cf0c1cc7ad284c7 100644
--- a/core/src/main/java/org/futo/circles/core/model/TimelineListItem.kt
+++ b/core/src/main/java/org/futo/circles/core/model/TimelineListItem.kt
@@ -23,7 +23,8 @@ data class TimelineHeaderItem(
 data class TimelineRoomListItem(
     override val id: String,
     val info: RoomInfo,
-    val isJoined: Boolean
+    val isJoined: Boolean,
+    val isLoading: Boolean = false
 ) : TimelineListItem()
 
 fun RoomSummary.toTimelineRoomListItem() = TimelineRoomListItem(
diff --git a/core/src/main/res/layout/list_item_users_timeline.xml b/core/src/main/res/layout/list_item_users_timeline.xml
index 89466acb71cfbb2848ea025fb5e6f469d4965567..13107bf01eaaf0673875e28d5119c2de409526db 100644
--- a/core/src/main/res/layout/list_item_users_timeline.xml
+++ b/core/src/main/res/layout/list_item_users_timeline.xml
@@ -62,4 +62,14 @@
         app:layout_constraintTop_toTopOf="parent"
         tools:visibility="visible" />
 
+    <ProgressBar
+        android:id="@+id/vLoading"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="16dp"
+        android:visibility="gone"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file