diff --git a/CHANGES.md b/CHANGES.md
index 4873a999b6..e1731433c3 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -3,6 +3,8 @@ Changes in Element 1.0.11 (2020-XX-XX)
 
 Features ✨:
  - Create DMs with users by scanning their QR code (#2025)
+ - Add Invite friends quick invite actions (#2348)
+ - Add friend by scanning QR code, show your code to friends (#2025)
 
 Improvements 🙌:
  - New room creation tile with quick action (#2346)
@@ -12,6 +14,7 @@ Improvements 🙌:
  - Handle events of type "m.room.server_acl" (#890)
  - Room creation form: add advanced section to disable federation (#1314)
  - Move "Enable Encryption" from room setting screen to room profile screen (#2394)
+ - Improve Invite user screen (seamless search for matrix ID)
 
 Bugfix 🐛:
  - Fix crash on AttachmentViewer (#2365)
diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
index 645fb55bb9..48705ee7b7 100644
--- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
+++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/auth/login/LoginWizard.kt
@@ -27,7 +27,7 @@ interface LoginWizard {
      * @param password the password field
      * @param deviceName the initial device name
      * @param callback  the matrix callback on which you'll receive the result of authentication.
-     * @return return a [Cancelable]
+     * @return a [Cancelable]
      */
     fun login(login: String,
               password: String,
diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml
index fb4764b3be..7d2ca11813 100644
--- a/vector/src/main/AndroidManifest.xml
+++ b/vector/src/main/AndroidManifest.xml
@@ -229,6 +229,7 @@
         <activity android:name=".features.widgets.WidgetActivity" />
         <activity android:name=".features.pin.PinActivity" />
         <activity android:name=".features.home.room.detail.search.SearchActivity" />
+        <activity android:name=".features.usercode.UserCodeActivity" />
 
         <!-- Services -->
 
diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
index acdad5407c..32c98922fb 100644
--- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt
@@ -111,8 +111,8 @@ import im.vector.app.features.settings.threepids.ThreePidsSettingsFragment
 import im.vector.app.features.share.IncomingShareFragment
 import im.vector.app.features.signout.soft.SoftLogoutFragment
 import im.vector.app.features.terms.ReviewTermsFragment
-import im.vector.app.features.userdirectory.KnownUsersFragment
-import im.vector.app.features.userdirectory.UserDirectoryFragment
+import im.vector.app.features.usercode.ShowUserCodeFragment
+import im.vector.app.features.userdirectory.UserListFragment
 import im.vector.app.features.widgets.WidgetFragment
 
 @Module
@@ -255,13 +255,8 @@ interface FragmentModule {
 
     @Binds
     @IntoMap
-    @FragmentKey(UserDirectoryFragment::class)
-    fun bindUserDirectoryFragment(fragment: UserDirectoryFragment): Fragment
-
-    @Binds
-    @IntoMap
-    @FragmentKey(KnownUsersFragment::class)
-    fun bindKnownUsersFragment(fragment: KnownUsersFragment): Fragment
+    @FragmentKey(UserListFragment::class)
+    fun bindUserListFragment(fragment: UserListFragment): Fragment
 
     @Binds
     @IntoMap
@@ -582,4 +577,9 @@ interface FragmentModule {
     @IntoMap
     @FragmentKey(SearchFragment::class)
     fun bindSearchFragment(fragment: SearchFragment): Fragment
+
+    @Binds
+    @IntoMap
+    @FragmentKey(ShowUserCodeFragment::class)
+    fun bindShowUserCodeFragment(fragment: ShowUserCodeFragment): Fragment
 }
diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
index fde40f9195..818a32fca3 100644
--- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
+++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt
@@ -50,6 +50,7 @@ import im.vector.app.features.invite.InviteUsersToRoomActivity
 import im.vector.app.features.invite.VectorInviteView
 import im.vector.app.features.link.LinkHandlerActivity
 import im.vector.app.features.login.LoginActivity
+import im.vector.app.features.matrixto.MatrixToBottomSheet
 import im.vector.app.features.media.BigImageViewerActivity
 import im.vector.app.features.media.VectorAttachmentViewerActivity
 import im.vector.app.features.navigation.Navigator
@@ -72,6 +73,7 @@ import im.vector.app.features.share.IncomingShareActivity
 import im.vector.app.features.signout.soft.SoftLogoutActivity
 import im.vector.app.features.terms.ReviewTermsActivity
 import im.vector.app.features.ui.UiStateRepository
+import im.vector.app.features.usercode.UserCodeActivity
 import im.vector.app.features.widgets.WidgetActivity
 import im.vector.app.features.widgets.permissions.RoomWidgetPermissionBottomSheet
 import im.vector.app.features.workers.signout.SignOutBottomSheetDialogFragment
@@ -140,6 +142,7 @@ interface ScreenComponent {
     fun inject(activity: VectorAttachmentViewerActivity)
     fun inject(activity: VectorJitsiActivity)
     fun inject(activity: SearchActivity)
+    fun inject(activity: UserCodeActivity)
 
     /* ==========================================================================================
      * BottomSheets
@@ -158,6 +161,7 @@ interface ScreenComponent {
     fun inject(bottomSheet: RoomWidgetsBottomSheet)
     fun inject(bottomSheet: CallControlsBottomSheet)
     fun inject(bottomSheet: SignOutBottomSheetDialogFragment)
+    fun inject(bottomSheet: MatrixToBottomSheet)
 
     /* ==========================================================================================
      * Others
diff --git a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt
index 836dab00c5..7ae8bc9c2e 100644
--- a/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt
+++ b/vector/src/main/java/im/vector/app/core/di/ViewModelModule.kt
@@ -35,7 +35,7 @@ import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedA
 import im.vector.app.features.reactions.EmojiChooserViewModel
 import im.vector.app.features.roomdirectory.RoomDirectorySharedActionViewModel
 import im.vector.app.features.roomprofile.RoomProfileSharedActionViewModel
-import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel
+import im.vector.app.features.userdirectory.UserListSharedActionViewModel
 
 @Module
 interface ViewModelModule {
@@ -87,8 +87,8 @@ interface ViewModelModule {
 
     @Binds
     @IntoMap
-    @ViewModelKey(UserDirectorySharedActionViewModel::class)
-    fun bindUserDirectorySharedActionViewModel(viewModel: UserDirectorySharedActionViewModel): ViewModel
+    @ViewModelKey(UserListSharedActionViewModel::class)
+    fun bindUserListSharedActionViewModel(viewModel: UserListSharedActionViewModel): ViewModel
 
     @Binds
     @IntoMap
diff --git a/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt b/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt
new file mode 100644
index 0000000000..4e53b293d3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/core/epoxy/CheckBoxItem.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.core.epoxy
+
+import android.widget.CompoundButton
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import com.google.android.material.checkbox.MaterialCheckBox
+import im.vector.app.R
+import kotlinx.android.synthetic.main.vector_preference_push_rule.view.*
+
+@EpoxyModelClass(layout = R.layout.item_checkbox)
+abstract class CheckBoxItem : VectorEpoxyModel<CheckBoxItem.Holder>() {
+
+    @EpoxyAttribute
+    var checked: Boolean = false
+
+    @EpoxyAttribute lateinit var title: String
+
+    @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
+    var checkChangeListener: CompoundButton.OnCheckedChangeListener? = null
+
+    override fun bind(holder: Holder) {
+        super.bind(holder)
+        holder.checkbox.isChecked = checked
+        holder.checkbox.text = title
+        holder.checkbox.setOnCheckedChangeListener(checkChangeListener)
+    }
+
+    class Holder : VectorEpoxyHolder() {
+        val checkbox by bind<MaterialCheckBox>(R.id.checkbox)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/core/extensions/EditText.kt b/vector/src/main/java/im/vector/app/core/extensions/EditText.kt
index 355dd8442f..05b70def3d 100644
--- a/vector/src/main/java/im/vector/app/core/extensions/EditText.kt
+++ b/vector/src/main/java/im/vector/app/core/extensions/EditText.kt
@@ -26,7 +26,7 @@ import androidx.annotation.DrawableRes
 import im.vector.app.R
 import im.vector.app.core.platform.SimpleTextWatcher
 
-fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_filter,
+fun EditText.setupAsSearch(@DrawableRes searchIconRes: Int = R.drawable.ic_search,
                            @DrawableRes clearIconRes: Int = R.drawable.ic_x_gray) {
     addTextChangedListener(object : SimpleTextWatcher() {
         override fun afterTextChanged(s: Editable) {
diff --git a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
index d228adab12..2348b07c7b 100644
--- a/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
+++ b/vector/src/main/java/im/vector/app/core/utils/SystemUtils.kt
@@ -136,13 +136,19 @@ fun startSharePlainTextIntent(fragment: Fragment,
                               activityResultLauncher: ActivityResultLauncher<Intent>?,
                               chooserTitle: String?,
                               text: String,
-                              subject: String? = null) {
+                              subject: String? = null,
+                              extraTitle: String? = null) {
     val share = Intent(Intent.ACTION_SEND)
     share.type = "text/plain"
     share.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT)
     // Add data to the intent, the receiving app will decide what to do with it.
     share.putExtra(Intent.EXTRA_SUBJECT, subject)
     share.putExtra(Intent.EXTRA_TEXT, text)
+
+    extraTitle?.let {
+        share.putExtra(Intent.EXTRA_TITLE, it)
+    }
+
     val intent = Intent.createChooser(share, chooserTitle)
     try {
         if (activityResultLauncher != null) {
diff --git a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
index 23d21f5240..6c3ec06f75 100644
--- a/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/contactsbook/ContactsBookFragment.kt
@@ -30,10 +30,10 @@ import im.vector.app.core.extensions.configureWith
 import im.vector.app.core.extensions.hideKeyboard
 import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.features.userdirectory.PendingInvitee
-import im.vector.app.features.userdirectory.UserDirectoryAction
-import im.vector.app.features.userdirectory.UserDirectorySharedAction
-import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel
-import im.vector.app.features.userdirectory.UserDirectoryViewModel
+import im.vector.app.features.userdirectory.UserListAction
+import im.vector.app.features.userdirectory.UserListSharedAction
+import im.vector.app.features.userdirectory.UserListSharedActionViewModel
+import im.vector.app.features.userdirectory.UserListViewModel
 import kotlinx.android.synthetic.main.fragment_contacts_book.*
 import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.api.session.user.model.User
@@ -46,16 +46,16 @@ class ContactsBookFragment @Inject constructor(
 ) : VectorBaseFragment(), ContactsBookController.Callback {
 
     override fun getLayoutResId() = R.layout.fragment_contacts_book
-    private val viewModel: UserDirectoryViewModel by activityViewModel()
+    private val viewModel: UserListViewModel by activityViewModel()
 
     // Use activityViewModel to avoid loading several times the data
     private val contactsBookViewModel: ContactsBookViewModel by activityViewModel()
 
-    private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
+    private lateinit var sharedActionViewModel: UserListSharedActionViewModel
 
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
+        sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java)
         setupRecyclerView()
         setupFilterView()
         setupConsentView()
@@ -110,7 +110,7 @@ class ContactsBookFragment @Inject constructor(
 
     private fun setupCloseView() {
         phoneBookClose.debouncedClicks {
-            sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
+            sharedActionViewModel.post(UserListSharedAction.GoBack)
         }
     }
 
@@ -122,13 +122,13 @@ class ContactsBookFragment @Inject constructor(
 
     override fun onMatrixIdClick(matrixId: String) {
         view?.hideKeyboard()
-        viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId))))
-        sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
+        viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId))))
+        sharedActionViewModel.post(UserListSharedAction.GoBack)
     }
 
     override fun onThreePidClick(threePid: ThreePid) {
         view?.hideKeyboard()
-        viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid)))
-        sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
+        viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid)))
+        sharedActionViewModel.post(UserListSharedAction.GoBack)
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
index 2035ee50f6..2e21d04d06 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomActivity.kt
@@ -45,23 +45,23 @@ import im.vector.app.core.utils.allGranted
 import im.vector.app.core.utils.checkPermissions
 import im.vector.app.features.contactsbook.ContactsBookFragment
 import im.vector.app.features.contactsbook.ContactsBookViewModel
-import im.vector.app.features.userdirectory.KnownUsersFragment
-import im.vector.app.features.userdirectory.KnownUsersFragmentArgs
-import im.vector.app.features.userdirectory.UserDirectoryFragment
-import im.vector.app.features.userdirectory.UserDirectorySharedAction
-import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel
-import im.vector.app.features.userdirectory.UserDirectoryViewModel
+import im.vector.app.features.userdirectory.UserListFragment
+import im.vector.app.features.userdirectory.UserListFragmentArgs
+import im.vector.app.features.userdirectory.UserListSharedAction
+import im.vector.app.features.userdirectory.UserListSharedActionViewModel
+import im.vector.app.features.userdirectory.UserListViewModel
+import im.vector.app.features.userdirectory.UserListViewState
 import kotlinx.android.synthetic.main.activity.*
 import org.matrix.android.sdk.api.failure.Failure
 import org.matrix.android.sdk.api.session.room.failure.CreateRoomFailure
 import java.net.HttpURLConnection
 import javax.inject.Inject
 
-class CreateDirectRoomActivity : SimpleFragmentActivity() {
+class CreateDirectRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory {
 
     private val viewModel: CreateDirectRoomViewModel by viewModel()
-    private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
-    @Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory
+    private lateinit var sharedActionViewModel: UserListSharedActionViewModel
+    @Inject lateinit var userListViewModelFactory: UserListViewModel.Factory
     @Inject lateinit var createDirectRoomViewModelFactory: CreateDirectRoomViewModel.Factory
     @Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory
     @Inject lateinit var errorFormatter: ErrorFormatter
@@ -71,37 +71,36 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
         injector.inject(this)
     }
 
+    override fun create(initialState: UserListViewState, args: UserListFragmentArgs): UserListViewModel {
+        return userListViewModelFactory.create(initialState, args)
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         toolbar.visibility = View.GONE
-        sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
-        if (intent?.getBooleanExtra(BY_QR_CODE, false)!!) {
-            if (isFirstCreation()) { openAddByQrCode() }
-        } else {
-            sharedActionViewModel
-                    .observe()
-                    .subscribe { sharedAction ->
-                        when (sharedAction) {
-                            UserDirectorySharedAction.OpenUsersDirectory    ->
-                                addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java)
-                            UserDirectorySharedAction.Close                 -> finish()
-                            UserDirectorySharedAction.GoBack                -> onBackPressed()
-                            is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction)
-                            UserDirectorySharedAction.OpenPhoneBook         -> openPhoneBook()
-                        }.exhaustive
-                    }
-                    .disposeOnDestroy()
-            if (isFirstCreation()) {
-                addFragment(
-                        R.id.container,
-                        KnownUsersFragment::class.java,
-                        KnownUsersFragmentArgs(
-                                title = getString(R.string.fab_menu_create_chat),
-                                menuResId = R.menu.vector_create_direct_room,
-                                isCreatingRoom = true
-                        )
-                )
-            }
+
+        sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
+        sharedActionViewModel
+                .observe()
+                .subscribe { action ->
+                    when (action) {
+                        UserListSharedAction.Close  -> finish()
+                        UserListSharedAction.GoBack -> onBackPressed()
+                        is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(action)
+                        UserListSharedAction.OpenPhoneBook -> openPhoneBook()
+                        UserListSharedAction.AddByQrCode -> openAddByQrCode()
+                    }.exhaustive
+                }
+                .disposeOnDestroy()
+        if (isFirstCreation()) {
+            addFragment(
+                    R.id.container,
+                    UserListFragment::class.java,
+                    UserListFragmentArgs(
+                            title = getString(R.string.fab_menu_create_chat),
+                            menuResId = R.menu.vector_create_direct_room
+                    )
+            )
         }
         viewModel.selectSubscribe(this, CreateDirectRoomViewState::createAndInviteState) {
             renderCreateAndInviteState(it)
@@ -129,22 +128,22 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
         if (allGranted(grantResults)) {
             if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
                 doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
-            } else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && intent?.getBooleanExtra(BY_QR_CODE, false)!!) {
+            } else if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
                 addFragment(R.id.container, CreateDirectRoomByQrCodeFragment::class.java)
             }
         } else {
             Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show()
-            if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA && intent?.getBooleanExtra(BY_QR_CODE, false)!!) {
+            if (requestCode == PERMISSION_REQUEST_CODE_LAUNCH_CAMERA) {
                 finish()
             }
         }
     }
 
-    private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) {
+    private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) {
         if (action.itemId == R.id.action_create_direct_room) {
             viewModel.handle(CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(
                     action.invitees,
-                    action.existingDmRoomId
+                    null
             ))
         }
     }
@@ -198,12 +197,9 @@ class CreateDirectRoomActivity : SimpleFragmentActivity() {
     }
 
     companion object {
-        private const val BY_QR_CODE = "BY_QR_CODE"
 
-        fun getIntent(context: Context, byQrCode: Boolean = false): Intent {
-            return Intent(context, CreateDirectRoomActivity::class.java).apply {
-                putExtra(BY_QR_CODE, byQrCode)
-            }
+        fun getIntent(context: Context): Intent {
+            return Intent(context, CreateDirectRoomActivity::class.java)
         }
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
index f03368fdd5..3fee3a3285 100644
--- a/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/createdirect/CreateDirectRoomByQrCodeFragment.kt
@@ -21,7 +21,11 @@ import com.airbnb.mvrx.activityViewModel
 import com.google.zxing.Result
 import com.google.zxing.ResultMetadataType
 import im.vector.app.R
+import im.vector.app.core.extensions.hideKeyboard
 import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
+import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.registerForPermissionsResult
 import im.vector.app.features.userdirectory.PendingInvitee
 import kotlinx.android.synthetic.main.fragment_qr_code_scanner.*
 import me.dm7.barcodescanner.zxing.ZXingScannerView
@@ -36,16 +40,32 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
 
     override fun getLayoutResId() = R.layout.fragment_qr_code_scanner
 
-    override fun onResume() {
-        super.onResume()
-        // Register ourselves as a handler for scan results.
-        scannerView.setResultHandler(null)
+    private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
+        if (allGranted) {
+            startCamera()
+        }
+    }
+
+    private fun startCamera() {
         // Start camera on resume
         scannerView.startCamera()
     }
 
+    override fun onResume() {
+        super.onResume()
+        view?.hideKeyboard()
+        // Register ourselves as a handler for scan results.
+        scannerView.setResultHandler(this)
+        // Start camera on resume
+        if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
+            startCamera()
+        }
+    }
+
     override fun onPause() {
         super.onPause()
+        // Unregister ourselves as a handler for scan results.
+        scannerView.setResultHandler(null)
         // Stop camera on pause
         scannerView.stopCamera()
     }
@@ -73,23 +93,17 @@ class CreateDirectRoomByQrCodeFragment @Inject constructor() : VectorBaseFragmen
             requireActivity().finish()
         } else {
             val existingDm = viewModel.session.getExistingDirectRoomWithUser(mxid)
-
-            if (existingDm === null) {
-                // The following assumes MXIDs are case insensitive
-                if (mxid.equals(other = viewModel.session.myUserId, ignoreCase = true)) {
-                    Toast.makeText(requireContext(), R.string.cannot_dm_self, Toast.LENGTH_SHORT).show()
-                    requireActivity().finish()
-                } else {
-                    // Try to get user from known users and fall back to creating a User object from MXID
-                    val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null)
-
-                    viewModel.handle(
-                            CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingInvitee.UserPendingInvitee(qrInvitee)))
-                    )
-                }
-            } else {
-                navigator.openRoom(requireContext(), existingDm, null, false)
+            // The following assumes MXIDs are case insensitive
+            if (mxid.equals(other = viewModel.session.myUserId, ignoreCase = true)) {
+                Toast.makeText(requireContext(), R.string.cannot_dm_self, Toast.LENGTH_SHORT).show()
                 requireActivity().finish()
+            } else {
+                // Try to get user from known users and fall back to creating a User object from MXID
+                val qrInvitee = if (viewModel.session.getUser(mxid) != null) viewModel.session.getUser(mxid)!! else User(mxid, null, null)
+
+                viewModel.handle(
+                        CreateDirectRoomAction.CreateRoomAndInviteSelectedUsers(setOf(PendingInvitee.UserPendingInvitee(qrInvitee)), existingDm)
+                )
             }
         }
     }
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
index e267248fc3..1a60d8e219 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt
@@ -18,15 +18,19 @@ package im.vector.app.features.home
 
 import android.os.Bundle
 import android.view.View
+import androidx.core.app.ActivityOptionsCompat
+import androidx.core.view.ViewCompat
 import androidx.core.view.isVisible
 import im.vector.app.BuildConfig
 import im.vector.app.R
 import im.vector.app.core.extensions.observeK
 import im.vector.app.core.extensions.replaceChildFragment
 import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.core.utils.startSharePlainTextIntent
 import im.vector.app.features.grouplist.GroupListFragment
 import im.vector.app.features.settings.VectorPreferences
 import im.vector.app.features.settings.VectorSettingsActivity
+import im.vector.app.features.usercode.UserCodeActivity
 import im.vector.app.features.workers.signout.SignOutUiWorker
 import kotlinx.android.synthetic.main.fragment_home_drawer.*
 import org.matrix.android.sdk.api.session.Session
@@ -75,6 +79,32 @@ class HomeDrawerFragment @Inject constructor(
             SignOutUiWorker(requireActivity()).perform()
         }
 
+        homeDrawerQRCodeButton.debouncedClicks {
+            UserCodeActivity.newIntent(requireContext(), sharedActionViewModel.session.myUserId).let {
+                val options =
+                        ActivityOptionsCompat.makeSceneTransitionAnimation(
+                                requireActivity(),
+                                homeDrawerHeaderAvatarView,
+                                ViewCompat.getTransitionName(homeDrawerHeaderAvatarView) ?: ""
+                        )
+                startActivity(it, options.toBundle())
+            }
+        }
+
+        homeDrawerInviteFriendButton.debouncedClicks {
+            session.permalinkService().createPermalink(sharedActionViewModel.session.myUserId)?.let { permalink ->
+                val text = getString(R.string.invite_friends_text, permalink)
+
+                startSharePlainTextIntent(
+                        fragment = this,
+                        activityResultLauncher = null,
+                        chooserTitle = getString(R.string.invite_friends),
+                        text = text,
+                        extraTitle = getString(R.string.invite_friends_rich_title)
+                )
+            }
+        }
+
         // Debug menu
         homeDrawerHeaderDebugView.isVisible = BuildConfig.DEBUG && vectorPreferences.developerMode()
         homeDrawerHeaderDebugView.debouncedClicks {
diff --git a/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt
index 58747a4c18..b695f48ee5 100644
--- a/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/home/HomeSharedActionViewModel.kt
@@ -17,6 +17,7 @@
 package im.vector.app.features.home
 
 import im.vector.app.core.platform.VectorSharedActionViewModel
+import org.matrix.android.sdk.api.session.Session
 import javax.inject.Inject
 
-class HomeSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<HomeActivitySharedAction>()
+class HomeSharedActionViewModel @Inject constructor(val session: Session) : VectorSharedActionViewModel<HomeActivitySharedAction>()
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
index e47072d0b0..60c2745b44 100644
--- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt
@@ -45,7 +45,6 @@ import im.vector.app.features.home.room.list.actions.RoomListActionsArgs
 import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet
 import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction
 import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedActionViewModel
-import im.vector.app.features.home.room.list.widget.DmsFabMenuView
 import im.vector.app.features.home.room.list.widget.NotifsFabMenuView
 import im.vector.app.features.notifications.NotificationDrawerManager
 import kotlinx.android.parcel.Parcelize
@@ -67,7 +66,7 @@ class RoomListFragment @Inject constructor(
         val roomListViewModelFactory: RoomListViewModel.Factory,
         private val notificationDrawerManager: NotificationDrawerManager,
         private val sharedViewPool: RecyclerView.RecycledViewPool
-) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, DmsFabMenuView.Listener, NotifsFabMenuView.Listener {
+) : VectorBaseFragment(), RoomSummaryController.Listener, OnBackPressed, NotifsFabMenuView.Listener {
 
     private var modelBuildListener: OnModelBuildFinishedListener? = null
     private lateinit var sharedActionViewModel: RoomListQuickActionsSharedActionViewModel
@@ -111,7 +110,6 @@ class RoomListFragment @Inject constructor(
             }.exhaustive
         }
 
-        createDmFabMenu.listener = this
         createChatFabMenu.listener = this
 
         sharedActionViewModel
@@ -130,7 +128,6 @@ class RoomListFragment @Inject constructor(
         roomListView.cleanup()
         roomController.listener = null
         stateRestorer.clear()
-        createDmFabMenu.listener = null
         createChatFabMenu.listener = null
         super.onDestroyView()
     }
@@ -142,32 +139,33 @@ class RoomListFragment @Inject constructor(
     private fun setupCreateRoomButton() {
         when (roomListParams.displayMode) {
             RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.isVisible = true
-            RoomListDisplayMode.PEOPLE        -> createDmFabMenu.isVisible = true
+            RoomListDisplayMode.PEOPLE        -> createChatRoomButton.isVisible = true
             RoomListDisplayMode.ROOMS         -> createGroupRoomButton.isVisible = true
             else                              -> Unit // No button in this mode
         }
 
+        createChatRoomButton.debouncedClicks {
+            createDirectChat()
+        }
         createGroupRoomButton.debouncedClicks {
             openRoomDirectory()
         }
 
-        // Hide FABs when list is scrolling
+        // Hide FAB when list is scrolling
         roomListView.addOnScrollListener(
                 object : RecyclerView.OnScrollListener() {
                     override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
-                        createDmFabMenu.removeCallbacks(showFabRunnable)
                         createChatFabMenu.removeCallbacks(showFabRunnable)
 
                         when (newState) {
                             RecyclerView.SCROLL_STATE_IDLE     -> {
-                                createDmFabMenu.postDelayed(showFabRunnable, 250)
                                 createChatFabMenu.postDelayed(showFabRunnable, 250)
                             }
                             RecyclerView.SCROLL_STATE_DRAGGING,
                             RecyclerView.SCROLL_STATE_SETTLING -> {
                                 when (roomListParams.displayMode) {
                                     RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.hide()
-                                    RoomListDisplayMode.PEOPLE        -> createDmFabMenu.hide()
+                                    RoomListDisplayMode.PEOPLE        -> createChatRoomButton.hide()
                                     RoomListDisplayMode.ROOMS         -> createGroupRoomButton.hide()
                                     else                              -> Unit
                                 }
@@ -192,10 +190,6 @@ class RoomListFragment @Inject constructor(
         navigator.openCreateDirectRoom(requireActivity())
     }
 
-    override fun createDirectChatByQrCode() {
-        navigator.openCreateDirectRoom(requireContext(), true)
-    }
-
     private fun setupRecyclerView() {
         val layoutManager = LinearLayoutManager(context)
         stateRestorer = LayoutManagerStateRestorer(layoutManager).register()
@@ -214,7 +208,7 @@ class RoomListFragment @Inject constructor(
         if (isAdded) {
             when (roomListParams.displayMode) {
                 RoomListDisplayMode.NOTIFICATIONS -> createChatFabMenu.show()
-                RoomListDisplayMode.PEOPLE        -> createDmFabMenu.show()
+                RoomListDisplayMode.PEOPLE        -> createChatRoomButton.show()
                 RoomListDisplayMode.ROOMS         -> createGroupRoomButton.show()
                 else                              -> Unit
             }
@@ -343,9 +337,6 @@ class RoomListFragment @Inject constructor(
     }
 
     override fun onBackPressed(toolbarButton: Boolean): Boolean {
-        if (createDmFabMenu.onBackPressed()) {
-            return true
-        }
         if (createChatFabMenu.onBackPressed()) {
             return true
         }
diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt b/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt
deleted file mode 100644
index 9659b7b12b..0000000000
--- a/vector/src/main/java/im/vector/app/features/home/room/list/widget/DmsFabMenuView.kt
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2019 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.home.room.list.widget
-
-import android.content.Context
-import android.util.AttributeSet
-import androidx.constraintlayout.motion.widget.MotionLayout
-import androidx.core.view.isVisible
-import com.google.android.material.floatingactionbutton.FloatingActionButton
-import im.vector.app.R
-import kotlinx.android.synthetic.main.motion_dms_fab_menu_merge.view.*
-
-class DmsFabMenuView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null,
-                                               defStyleAttr: Int = 0) : MotionLayout(context, attrs, defStyleAttr) {
-
-    var listener: Listener? = null
-
-    init {
-        inflate(context, R.layout.motion_dms_fab_menu_merge, this)
-    }
-
-    override fun onFinishInflate() {
-        super.onFinishInflate()
-
-        listOf(createDmByMxid, createDmByMxidLabel)
-                .forEach {
-                    it.setOnClickListener {
-                        closeFabMenu()
-                        listener?.createDirectChat()
-                    }
-                }
-        listOf(createDmByQrCode, createDmByQrCodeLabel)
-                .forEach {
-                    it.setOnClickListener {
-                        closeFabMenu()
-                        listener?.createDirectChatByQrCode()
-                    }
-                }
-
-        dmsCreateRoomTouchGuard.setOnClickListener {
-            closeFabMenu()
-        }
-    }
-
-    override fun transitionToEnd() {
-        super.transitionToEnd()
-
-        dmsCreateRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_close)
-    }
-
-    override fun transitionToStart() {
-        super.transitionToStart()
-
-        dmsCreateRoomButton.contentDescription = context.getString(R.string.a11y_create_menu_open)
-    }
-
-    fun show() {
-        isVisible = true
-        dmsCreateRoomButton.show()
-    }
-
-    fun hide() {
-        dmsCreateRoomButton.hide(object : FloatingActionButton.OnVisibilityChangedListener() {
-            override fun onHidden(fab: FloatingActionButton?) {
-                super.onHidden(fab)
-                isVisible = false
-            }
-        })
-    }
-
-    private fun closeFabMenu() {
-        transitionToStart()
-    }
-
-    fun onBackPressed(): Boolean {
-        if (currentState == R.id.constraint_set_fab_menu_open) {
-            closeFabMenu()
-            return true
-        }
-
-        return false
-    }
-
-    interface Listener {
-        fun createDirectChat()
-        fun createDirectChatByQrCode()
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt
index f6c60f6a6d..fe7a8006e0 100644
--- a/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/homeserver/HomeServerCapabilitiesViewModel.kt
@@ -28,7 +28,7 @@ import im.vector.app.core.platform.EmptyViewEvents
 import im.vector.app.core.platform.VectorViewModel
 import im.vector.app.features.raw.wellknown.getElementWellknown
 import im.vector.app.features.raw.wellknown.isE2EByDefault
-import im.vector.app.features.userdirectory.KnownUsersFragment
+import im.vector.app.features.userdirectory.UserListFragment
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import org.matrix.android.sdk.api.extensions.tryOrNull
@@ -50,7 +50,7 @@ class HomeServerCapabilitiesViewModel @AssistedInject constructor(
     companion object : MvRxViewModelFactory<HomeServerCapabilitiesViewModel, HomeServerCapabilitiesViewState> {
         @JvmStatic
         override fun create(viewModelContext: ViewModelContext, state: HomeServerCapabilitiesViewState): HomeServerCapabilitiesViewModel? {
-            val fragment: KnownUsersFragment = (viewModelContext as FragmentViewModelContext).fragment()
+            val fragment: UserListFragment = (viewModelContext as FragmentViewModelContext).fragment()
             return fragment.homeServerCapabilitiesViewModelFactory.create(state)
         }
 
diff --git a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
index 087b7c2f55..513fbb5d83 100644
--- a/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
+++ b/vector/src/main/java/im/vector/app/features/invite/InviteUsersToRoomActivity.kt
@@ -21,6 +21,7 @@ import android.content.Intent
 import android.os.Bundle
 import android.os.Parcelable
 import android.view.View
+import android.widget.Toast
 import androidx.appcompat.app.AlertDialog
 import com.airbnb.mvrx.MvRx
 import com.airbnb.mvrx.viewModel
@@ -29,7 +30,6 @@ import im.vector.app.core.di.ScreenComponent
 import im.vector.app.core.error.ErrorFormatter
 import im.vector.app.core.extensions.addFragment
 import im.vector.app.core.extensions.addFragmentToBackstack
-import im.vector.app.core.extensions.exhaustive
 import im.vector.app.core.platform.SimpleFragmentActivity
 import im.vector.app.core.platform.WaitingViewData
 import im.vector.app.core.utils.PERMISSIONS_FOR_MEMBERS_SEARCH
@@ -39,12 +39,12 @@ import im.vector.app.core.utils.checkPermissions
 import im.vector.app.core.utils.toast
 import im.vector.app.features.contactsbook.ContactsBookFragment
 import im.vector.app.features.contactsbook.ContactsBookViewModel
-import im.vector.app.features.userdirectory.KnownUsersFragment
-import im.vector.app.features.userdirectory.KnownUsersFragmentArgs
-import im.vector.app.features.userdirectory.UserDirectoryFragment
-import im.vector.app.features.userdirectory.UserDirectorySharedAction
-import im.vector.app.features.userdirectory.UserDirectorySharedActionViewModel
-import im.vector.app.features.userdirectory.UserDirectoryViewModel
+import im.vector.app.features.userdirectory.UserListFragment
+import im.vector.app.features.userdirectory.UserListFragmentArgs
+import im.vector.app.features.userdirectory.UserListSharedAction
+import im.vector.app.features.userdirectory.UserListSharedActionViewModel
+import im.vector.app.features.userdirectory.UserListViewModel
+import im.vector.app.features.userdirectory.UserListViewState
 import kotlinx.android.parcel.Parcelize
 import kotlinx.android.synthetic.main.activity.*
 import org.matrix.android.sdk.api.failure.Failure
@@ -54,11 +54,11 @@ import javax.inject.Inject
 @Parcelize
 data class InviteUsersToRoomArgs(val roomId: String) : Parcelable
 
-class InviteUsersToRoomActivity : SimpleFragmentActivity() {
+class InviteUsersToRoomActivity : SimpleFragmentActivity(), UserListViewModel.Factory {
 
     private val viewModel: InviteUsersToRoomViewModel by viewModel()
-    private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
-    @Inject lateinit var userDirectoryViewModelFactory: UserDirectoryViewModel.Factory
+    private lateinit var sharedActionViewModel: UserListSharedActionViewModel
+    @Inject lateinit var userListViewModelFactory: UserListViewModel.Factory
     @Inject lateinit var inviteUsersToRoomViewModelFactory: InviteUsersToRoomViewModel.Factory
     @Inject lateinit var contactsBookViewModelFactory: ContactsBookViewModel.Factory
     @Inject lateinit var errorFormatter: ErrorFormatter
@@ -68,32 +68,40 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
         injector.inject(this)
     }
 
+    override fun create(initialState: UserListViewState, args: UserListFragmentArgs): UserListViewModel {
+        return userListViewModelFactory.create(initialState, args)
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
         toolbar.visibility = View.GONE
-        sharedActionViewModel = viewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
+
+        sharedActionViewModel = viewModelProvider.get(UserListSharedActionViewModel::class.java)
         sharedActionViewModel
                 .observe()
                 .subscribe { sharedAction ->
                     when (sharedAction) {
-                        UserDirectorySharedAction.OpenUsersDirectory    ->
-                            addFragmentToBackstack(R.id.container, UserDirectoryFragment::class.java)
-                        UserDirectorySharedAction.Close                 -> finish()
-                        UserDirectorySharedAction.GoBack                -> onBackPressed()
-                        is UserDirectorySharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction)
-                        UserDirectorySharedAction.OpenPhoneBook         -> openPhoneBook()
-                    }.exhaustive
+                        UserListSharedAction.Close                 -> finish()
+                        UserListSharedAction.GoBack                -> onBackPressed()
+                        is UserListSharedAction.OnMenuItemSelected -> onMenuItemSelected(sharedAction)
+                        UserListSharedAction.OpenPhoneBook         -> openPhoneBook()
+                        // not exhaustive because it's a sharedAction
+                        else                                       -> {
+                        }
+                    }
                 }
                 .disposeOnDestroy()
         if (isFirstCreation()) {
+            val args: InviteUsersToRoomArgs? = intent.extras?.getParcelable(MvRx.KEY_ARG)
             addFragment(
                     R.id.container,
-                    KnownUsersFragment::class.java,
-                    KnownUsersFragmentArgs(
+                    UserListFragment::class.java,
+                    UserListFragmentArgs(
                             title = getString(R.string.invite_users_to_room_title),
                             menuResId = R.menu.vector_invite_users_to_room,
-                            excludedUserIds = viewModel.getUserIdsOfRoomMembers()
+                            excludedUserIds = viewModel.getUserIdsOfRoomMembers(),
+                            existingRoomId = args?.roomId
                     )
             )
         }
@@ -101,6 +109,12 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
         viewModel.observeViewEvents { renderInviteEvents(it) }
     }
 
+    private fun onMenuItemSelected(action: UserListSharedAction.OnMenuItemSelected) {
+        if (action.itemId == R.id.action_invite_users_to_room_invite) {
+            viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.invitees))
+        }
+    }
+
     private fun openPhoneBook() {
         // Check permission first
         if (checkPermissions(PERMISSIONS_FOR_MEMBERS_SEARCH,
@@ -117,12 +131,8 @@ class InviteUsersToRoomActivity : SimpleFragmentActivity() {
             if (requestCode == PERMISSION_REQUEST_CODE_READ_CONTACTS) {
                 doOnPostResume { addFragmentToBackstack(R.id.container, ContactsBookFragment::class.java) }
             }
-        }
-    }
-
-    private fun onMenuItemSelected(action: UserDirectorySharedAction.OnMenuItemSelected) {
-        if (action.itemId == R.id.action_invite_users_to_room_invite) {
-            viewModel.handle(InviteUsersToRoomAction.InviteSelectedUsers(action.invitees))
+        } else {
+            Toast.makeText(baseContext, R.string.missing_permissions_error, Toast.LENGTH_SHORT).show()
         }
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt
new file mode 100644
index 0000000000..3f3706699f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/matrixto/MatrixToBottomSheet.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.matrixto
+
+import android.os.Bundle
+import android.view.View
+import im.vector.app.R
+import im.vector.app.core.di.ScreenComponent
+import im.vector.app.core.extensions.setTextOrHide
+import im.vector.app.core.platform.VectorBaseBottomSheetDialogFragment
+import im.vector.app.features.home.AvatarRenderer
+import kotlinx.android.synthetic.main.fragment_matrix_to_card.*
+import org.matrix.android.sdk.api.util.MatrixItem
+import javax.inject.Inject
+
+class MatrixToBottomSheet(private val matrixItem: MatrixItem) : VectorBaseBottomSheetDialogFragment() {
+
+    @Inject lateinit var avatarRenderer: AvatarRenderer
+
+    interface InteractionListener {
+        fun didTapStartMessage(matrixItem: MatrixItem)
+    }
+
+    override fun injectWith(injector: ScreenComponent) {
+        injector.inject(this)
+    }
+
+    private var interactionListener: InteractionListener? = null
+
+    override fun getLayoutResId() = R.layout.fragment_matrix_to_card
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        matrixToCardSendMessageButton.debouncedClicks {
+            interactionListener?.didTapStartMessage(matrixItem)
+            dismiss()
+        }
+
+        matrixToCardNameText.setTextOrHide(matrixItem.displayName)
+        matrixToCardUserIdText.setTextOrHide(matrixItem.id)
+        avatarRenderer.render(matrixItem, matrixToCardAvatar)
+    }
+
+    companion object {
+        const val ARGS = "MatrixToFragment.Args"
+
+        fun create(matrixItem: MatrixItem, listener: InteractionListener?): MatrixToBottomSheet {
+            return MatrixToBottomSheet(matrixItem).apply {
+                interactionListener = listener
+            }
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
index 9ff103113f..2d0ca86d52 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt
@@ -203,8 +203,8 @@ class DefaultNavigator @Inject constructor(
         context.startActivity(intent)
     }
 
-    override fun openCreateDirectRoom(context: Context, byQrCode: Boolean) {
-        val intent = CreateDirectRoomActivity.getIntent(context, byQrCode)
+    override fun openCreateDirectRoom(context: Context) {
+        val intent = CreateDirectRoomActivity.getIntent(context)
         context.startActivity(intent)
     }
 
diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
index 23d24b709c..504fccb63a 100644
--- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
+++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt
@@ -56,7 +56,7 @@ interface Navigator {
 
     fun openCreateRoom(context: Context, initialName: String = "")
 
-    fun openCreateDirectRoom(context: Context, byQrCode: Boolean = false)
+    fun openCreateDirectRoom(context: Context)
 
     fun openInviteUsersToRoom(context: Context, roomId: String)
 
diff --git a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt
index 2e91091443..e29c197ab8 100644
--- a/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt
+++ b/vector/src/main/java/im/vector/app/features/roommemberprofile/RoomMemberProfileController.kt
@@ -79,6 +79,17 @@ class RoomMemberProfileController @Inject constructor(
                 divider = false,
                 action = { callback?.onIgnoreClicked() }
         )
+        if (!state.isMine) {
+            buildProfileSection(stringProvider.getString(R.string.room_profile_section_more))
+
+            buildProfileAction(
+                    id = "direct",
+                    editable = false,
+                    title = stringProvider.getString(R.string.room_member_open_or_create_dm),
+                    dividerColor = dividerColor,
+                    action = { callback?.onOpenDmClicked() }
+            )
+        }
     }
 
     private fun buildRoomMemberActions(state: RoomMemberProfileViewState) {
diff --git a/vector/src/main/java/im/vector/app/features/usercode/QRCodeBitmapDecodeHelper.kt b/vector/src/main/java/im/vector/app/features/usercode/QRCodeBitmapDecodeHelper.kt
new file mode 100644
index 0000000000..178a283d2c
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/QRCodeBitmapDecodeHelper.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.usercode
+
+import android.graphics.Bitmap
+import com.google.zxing.BarcodeFormat
+import com.google.zxing.BinaryBitmap
+import com.google.zxing.DecodeHintType
+import com.google.zxing.LuminanceSource
+import com.google.zxing.MultiFormatReader
+import com.google.zxing.RGBLuminanceSource
+import com.google.zxing.ReaderException
+import com.google.zxing.Result
+import com.google.zxing.common.HybridBinarizer
+
+// Some helper code from BinaryEye
+object QRCodeBitmapDecodeHelper {
+
+    private val multiFormatReader = MultiFormatReader()
+    private val decoderHints = mapOf(DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE))
+
+    fun decodeQRFromBitmap(bitmap: Bitmap): Result? =
+            decode(bitmap, false) ?: decode(bitmap, true)
+
+    private fun decode(bitmap: Bitmap, invert: Boolean = false): Result? {
+        val pixels = IntArray(bitmap.width * bitmap.height)
+        return decode(pixels, bitmap, invert)
+    }
+
+    private fun decode(
+            pixels: IntArray,
+            bitmap: Bitmap,
+            invert: Boolean = false
+    ): Result? {
+        val width = bitmap.width
+        val height = bitmap.height
+        if (bitmap.config != Bitmap.Config.ARGB_8888) {
+            bitmap.copy(Bitmap.Config.ARGB_8888, true)
+        } else {
+            bitmap
+        }.getPixels(pixels, 0, width, 0, 0, width, height)
+        return decodeLuminanceSource(
+                RGBLuminanceSource(width, height, pixels),
+                invert
+        )
+    }
+
+    private fun decodeLuminanceSource(
+            source: LuminanceSource,
+            invert: Boolean
+    ): Result? {
+        return decodeLuminanceSource(
+                if (invert) {
+                    source.invert()
+                } else {
+                    source
+                }
+        )
+    }
+
+    private fun decodeLuminanceSource(source: LuminanceSource): Result? {
+        val bitmap = BinaryBitmap(HybridBinarizer(source))
+        return try {
+            multiFormatReader.decode(bitmap, decoderHints)
+        } catch (e: ReaderException) {
+            null
+        } finally {
+            multiFormatReader.reset()
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt
new file mode 100644
index 0000000000..8b4820b06d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/ScanUserCodeFragment.kt
@@ -0,0 +1,135 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.usercode
+
+import android.app.Activity
+import android.os.Bundle
+import android.view.View
+import android.widget.Toast
+import com.airbnb.mvrx.activityViewModel
+import com.google.zxing.Result
+import com.google.zxing.ResultMetadataType
+import im.vector.app.R
+import im.vector.app.core.extensions.registerStartForActivityResult
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.core.utils.PERMISSIONS_FOR_TAKING_PHOTO
+import im.vector.app.core.utils.checkPermissions
+import im.vector.app.core.utils.registerForPermissionsResult
+import im.vector.lib.multipicker.MultiPicker
+import im.vector.lib.multipicker.utils.ImageUtils
+import kotlinx.android.synthetic.main.fragment_qr_code_scanner_with_button.*
+import me.dm7.barcodescanner.zxing.ZXingScannerView
+import org.matrix.android.sdk.api.extensions.tryOrNull
+import javax.inject.Inject
+
+class ScanUserCodeFragment @Inject constructor()
+    : VectorBaseFragment(),
+        ZXingScannerView.ResultHandler {
+
+    override fun getLayoutResId() = R.layout.fragment_qr_code_scanner_with_button
+
+    val sharedViewModel: UserCodeSharedViewModel by activityViewModel()
+
+    var autoFocus = true
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        userCodeMyCodeButton.debouncedClicks {
+            sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
+        }
+
+        userCodeOpenGalleryButton.debouncedClicks {
+            MultiPicker.get(MultiPicker.IMAGE).single().startWith(pickImageActivityResultLauncher)
+        }
+    }
+
+    private val openCameraActivityResultLauncher = registerForPermissionsResult { allGranted ->
+        if (allGranted) {
+            startCamera()
+        }
+    }
+
+    private val pickImageActivityResultLauncher = registerStartForActivityResult { activityResult ->
+        if (activityResult.resultCode == Activity.RESULT_OK) {
+            MultiPicker
+                    .get(MultiPicker.IMAGE)
+                    .getSelectedFiles(requireActivity(), activityResult.data)
+                    .firstOrNull()
+                    ?.contentUri
+                    ?.let { uri ->
+                        // try to see if it is a valid matrix code
+                        val bitmap = ImageUtils.getBitmap(requireContext(), uri)
+                                ?: return@let Unit.also {
+                                    Toast.makeText(requireContext(), getString(R.string.qr_code_not_scanned), Toast.LENGTH_SHORT).show()
+                                }
+                        handleResult(tryOrNull { QRCodeBitmapDecodeHelper.decodeQRFromBitmap(bitmap) })
+                    }
+        }
+    }
+
+    private fun startCamera() {
+        userCodeScannerView.startCamera()
+        userCodeScannerView.setAutoFocus(autoFocus)
+        userCodeScannerView.debouncedClicks {
+            this.autoFocus = !autoFocus
+            userCodeScannerView.setAutoFocus(autoFocus)
+        }
+    }
+
+    override fun onResume() {
+        super.onResume()
+        // Register ourselves as a handler for scan results.
+        userCodeScannerView.setResultHandler(this)
+        // Start camera on resume
+        if (checkPermissions(PERMISSIONS_FOR_TAKING_PHOTO, requireActivity(), openCameraActivityResultLauncher)) {
+            startCamera()
+        }
+    }
+
+    override fun onPause() {
+        super.onPause()
+        // Stop camera on pause
+        userCodeScannerView.stopCamera()
+    }
+
+    override fun handleResult(result: Result?) {
+        if (result === null) {
+            Toast.makeText(requireContext(), R.string.qr_code_not_scanned, Toast.LENGTH_SHORT).show()
+            requireActivity().finish()
+        } else {
+            val rawBytes = getRawBytes(result)
+            val rawBytesStr = rawBytes?.toString(Charsets.ISO_8859_1)
+            val value = rawBytesStr ?: result.text
+            sharedViewModel.handle(UserCodeActions.DecodedQRCode(value))
+        }
+    }
+
+    // Copied from https://github.com/markusfisch/BinaryEye/blob/
+    // 9d57889b810dcaa1a91d7278fc45c262afba1284/app/src/main/kotlin/de/markusfisch/android/binaryeye/activity/CameraActivity.kt#L434
+    private fun getRawBytes(result: Result): ByteArray? {
+        val metadata = result.resultMetadata ?: return null
+        val segments = metadata[ResultMetadataType.BYTE_SEGMENTS] ?: return null
+        var bytes = ByteArray(0)
+        @Suppress("UNCHECKED_CAST")
+        for (seg in segments as Iterable<ByteArray>) {
+            bytes += seg
+        }
+        // byte segments can never be shorter than the text.
+        // Zxing cuts off content prefixes like "WIFI:"
+        return if (bytes.size >= result.text.length) bytes else null
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt
new file mode 100644
index 0000000000..ab88f79bef
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/ShowUserCodeFragment.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.usercode
+
+import android.os.Bundle
+import android.view.View
+import com.airbnb.mvrx.activityViewModel
+import com.airbnb.mvrx.withState
+import im.vector.app.R
+import im.vector.app.core.extensions.setTextOrHide
+import im.vector.app.core.platform.VectorBaseFragment
+import im.vector.app.features.home.AvatarRenderer
+import kotlinx.android.synthetic.main.fragment_user_code_show.*
+import javax.inject.Inject
+
+class ShowUserCodeFragment @Inject constructor(
+        private val avatarRenderer: AvatarRenderer
+) : VectorBaseFragment() {
+
+    override fun getLayoutResId() = R.layout.fragment_user_code_show
+
+    val sharedViewModel: UserCodeSharedViewModel by activityViewModel()
+
+    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+        super.onViewCreated(view, savedInstanceState)
+        showUserCodeClose.debouncedClicks {
+            sharedViewModel.handle(UserCodeActions.DismissAction)
+        }
+        showUserCodeScanButton.debouncedClicks {
+            doOpenQRCodeScanner()
+        }
+    }
+
+    private fun doOpenQRCodeScanner() {
+        sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SCAN))
+    }
+
+    override fun invalidate() = withState(sharedViewModel) { state ->
+        state.matrixItem?.let { avatarRenderer.render(it, showUserCodeAvatar) }
+        state.shareLink?.let { showUserCodeQRImage.setData(it) }
+        showUserCodeCardNameText.setTextOrHide(state.matrixItem?.displayName)
+        showUserCodeCardUserIdText.setTextOrHide(state.matrixItem?.id)
+        Unit
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt
new file mode 100644
index 0000000000..0611e0f8c3
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActions.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.usercode
+
+import im.vector.app.core.platform.VectorViewModelAction
+import org.matrix.android.sdk.api.util.MatrixItem
+
+sealed class UserCodeActions : VectorViewModelAction {
+    object DismissAction : UserCodeActions()
+    data class SwitchMode(val mode: UserCodeState.Mode) : UserCodeActions()
+    data class DecodedQRCode(val code: String) : UserCodeActions()
+    data class StartChattingWithUser(val matrixItem: MatrixItem) : UserCodeActions()
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
new file mode 100644
index 0000000000..388dc220a8
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeActivity.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.usercode
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.Parcelable
+import android.widget.Toast
+import androidx.core.app.ActivityCompat
+import androidx.core.view.isVisible
+import androidx.fragment.app.Fragment
+import com.airbnb.mvrx.MvRx
+import com.airbnb.mvrx.viewModel
+import com.airbnb.mvrx.withState
+import im.vector.app.R
+import im.vector.app.core.di.ScreenComponent
+import im.vector.app.core.extensions.exhaustive
+import im.vector.app.core.platform.VectorBaseActivity
+import im.vector.app.features.matrixto.MatrixToBottomSheet
+import kotlinx.android.parcel.Parcelize
+import kotlinx.android.synthetic.main.activity_simple.*
+import org.matrix.android.sdk.api.util.MatrixItem
+import javax.inject.Inject
+import kotlin.reflect.KClass
+
+class UserCodeActivity
+    : VectorBaseActivity(), UserCodeSharedViewModel.Factory, MatrixToBottomSheet.InteractionListener {
+
+    @Inject lateinit var viewModelFactory: UserCodeSharedViewModel.Factory
+
+    val sharedViewModel: UserCodeSharedViewModel by viewModel()
+
+    @Parcelize
+    data class Args(
+            val userId: String
+    ) : Parcelable
+
+    override fun getLayoutRes() = R.layout.activity_simple
+
+    override fun injectWith(injector: ScreenComponent) {
+        injector.inject(this)
+    }
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        if (isFirstCreation()) {
+            // should be there early for shared element transition
+            showFragment(ShowUserCodeFragment::class, Bundle.EMPTY)
+        }
+
+        sharedViewModel.selectSubscribe(this, UserCodeState::mode) { mode ->
+            when (mode) {
+                UserCodeState.Mode.SHOW -> showFragment(ShowUserCodeFragment::class, Bundle.EMPTY)
+                UserCodeState.Mode.SCAN -> showFragment(ScanUserCodeFragment::class, Bundle.EMPTY)
+                is UserCodeState.Mode.RESULT -> {
+                    showFragment(ShowUserCodeFragment::class, Bundle.EMPTY)
+                    MatrixToBottomSheet.create(mode.matrixItem, this).show(supportFragmentManager, "MatrixToBottomSheet")
+                }
+            }
+        }
+
+        sharedViewModel.observeViewEvents {
+            when (it) {
+                is UserCodeShareViewEvents.InviteFriend -> TODO()
+                UserCodeShareViewEvents.Dismiss -> ActivityCompat.finishAfterTransition(this)
+                UserCodeShareViewEvents.ShowWaitingScreen -> simpleActivityWaitingView.isVisible = true
+                UserCodeShareViewEvents.HideWaitingScreen -> simpleActivityWaitingView.isVisible = false
+                is UserCodeShareViewEvents.ToastMessage -> Toast.makeText(this, it.message, Toast.LENGTH_LONG).show()
+                is UserCodeShareViewEvents.NavigateToRoom -> navigator.openRoom(this, it.roomId)
+            }.exhaustive
+        }
+    }
+
+    private fun showFragment(fragmentClass: KClass<out Fragment>, bundle: Bundle) {
+        if (supportFragmentManager.findFragmentByTag(fragmentClass.simpleName) == null) {
+            supportFragmentManager.beginTransaction().let {
+                it.setCustomAnimations(R.anim.fade_in, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+                it.replace(R.id.simpleFragmentContainer,
+                        fragmentClass.java,
+                        bundle,
+                        fragmentClass.simpleName
+                )
+                it.commit()
+            }
+        }
+    }
+
+    override fun didTapStartMessage(matrixItem: MatrixItem) {
+        sharedViewModel.handle(UserCodeActions.StartChattingWithUser(matrixItem))
+    }
+
+    override fun onBackPressed() = withState(sharedViewModel) {
+        when (it.mode) {
+            UserCodeState.Mode.SHOW -> super.onBackPressed()
+            is UserCodeState.Mode.RESULT,
+            UserCodeState.Mode.SCAN -> sharedViewModel.handle(UserCodeActions.SwitchMode(UserCodeState.Mode.SHOW))
+        }
+    }
+
+    override fun create(initialState: UserCodeState, args: Args) =
+            viewModelFactory.create(initialState, args)
+
+    companion object {
+        fun newIntent(context: Context, userId: String): Intent {
+            return Intent(context, UserCodeActivity::class.java).apply {
+                putExtra(MvRx.KEY_ARG, Args(userId))
+            }
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt
new file mode 100644
index 0000000000..26fcffadd2
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeShareViewEvents.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.usercode
+
+import im.vector.app.core.platform.VectorViewEvents
+
+sealed class UserCodeShareViewEvents : VectorViewEvents {
+    data class InviteFriend(val permalink: String) : UserCodeShareViewEvents()
+    object Dismiss : UserCodeShareViewEvents()
+    object ShowWaitingScreen : UserCodeShareViewEvents()
+    object HideWaitingScreen : UserCodeShareViewEvents()
+    data class ToastMessage(val message: String) : UserCodeShareViewEvents()
+    data class NavigateToRoom(val roomId: String) : UserCodeShareViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt
new file mode 100644
index 0000000000..17dd97cffa
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeSharedViewModel.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.usercode
+
+import androidx.lifecycle.viewModelScope
+import com.airbnb.mvrx.ActivityViewModelContext
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.ViewModelContext
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import im.vector.app.R
+import im.vector.app.core.platform.VectorViewModel
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.features.raw.wellknown.getElementWellknown
+import im.vector.app.features.raw.wellknown.isE2EByDefault
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import org.matrix.android.sdk.api.raw.RawService
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.permalinks.PermalinkData
+import org.matrix.android.sdk.api.session.permalinks.PermalinkParser
+import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
+import org.matrix.android.sdk.api.session.user.model.User
+import org.matrix.android.sdk.api.util.toMatrixItem
+import org.matrix.android.sdk.internal.util.awaitCallback
+
+class UserCodeSharedViewModel @AssistedInject constructor(
+        @Assisted val initialState: UserCodeState,
+        @Assisted val args: UserCodeActivity.Args,
+        private val session: Session,
+        private val stringProvider: StringProvider,
+        private val rawService: RawService) : VectorViewModel<UserCodeState, UserCodeActions, UserCodeShareViewEvents>(initialState) {
+
+    companion object : MvRxViewModelFactory<UserCodeSharedViewModel, UserCodeState> {
+        override fun create(viewModelContext: ViewModelContext, state: UserCodeState): UserCodeSharedViewModel? {
+            val args = viewModelContext.args<UserCodeActivity.Args>()
+            val factory = when (viewModelContext) {
+                is FragmentViewModelContext -> viewModelContext.fragment as? Factory
+                is ActivityViewModelContext -> viewModelContext.activity as? Factory
+            }
+            return factory?.create(state, args) ?: error("You should let your activity/fragment implements Factory interface")
+        }
+
+        override fun initialState(viewModelContext: ViewModelContext): UserCodeState? {
+            return UserCodeState(viewModelContext.args<UserCodeActivity.Args>().userId)
+        }
+    }
+
+    init {
+        val user = session.getUser(args.userId)
+        setState {
+            copy(
+                    matrixItem = user?.toMatrixItem(),
+                    shareLink = session.permalinkService().createPermalink(args.userId)
+            )
+        }
+    }
+
+    private fun handleInviteFriend() {
+        session.permalinkService().createPermalink(initialState.userId)?.let { permalink ->
+            _viewEvents.post(UserCodeShareViewEvents.InviteFriend(permalink))
+        }
+    }
+
+    @AssistedInject.Factory
+    interface Factory {
+        fun create(initialState: UserCodeState, args: UserCodeActivity.Args): UserCodeSharedViewModel
+    }
+
+    override fun handle(action: UserCodeActions) {
+        when (action) {
+            UserCodeActions.DismissAction -> _viewEvents.post(UserCodeShareViewEvents.Dismiss)
+            is UserCodeActions.SwitchMode -> setState { copy(mode = action.mode) }
+            is UserCodeActions.DecodedQRCode -> handleQrCodeDecoded(action)
+            is UserCodeActions.StartChattingWithUser -> handleStartChatting(action)
+        }
+    }
+
+    private fun handleStartChatting(withUser: UserCodeActions.StartChattingWithUser) {
+        val mxId = withUser.matrixItem.id
+        val existing = session.getExistingDirectRoomWithUser(mxId)
+        setState {
+            copy(mode = UserCodeState.Mode.SHOW)
+        }
+        if (existing != null) {
+            // navigate to this room
+            _viewEvents.post(UserCodeShareViewEvents.NavigateToRoom(existing))
+        } else {
+            // we should create the room then navigate
+            _viewEvents.post(UserCodeShareViewEvents.ShowWaitingScreen)
+            viewModelScope.launch(Dispatchers.IO) {
+                val adminE2EByDefault = rawService.getElementWellknown(session.myUserId)
+                        ?.isE2EByDefault()
+                        ?: true
+
+                val roomParams = CreateRoomParams()
+                        .apply {
+                            invitedUserIds.add(mxId)
+                            setDirectMessage()
+                            enableEncryptionIfInvitedUsersSupportIt = adminE2EByDefault
+                        }
+
+                val roomId =
+                        try {
+                            awaitCallback<String> { session.createRoom(roomParams, it) }
+                        } catch (failure: Throwable) {
+                            _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.invite_users_to_room_failure)))
+                            return@launch
+                        } finally {
+                            _viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen)
+                        }
+                _viewEvents.post(UserCodeShareViewEvents.NavigateToRoom(roomId))
+            }
+        }
+    }
+
+    private fun handleQrCodeDecoded(action: UserCodeActions.DecodedQRCode) {
+        val linkedId = PermalinkParser.parse(action.code)
+        if (linkedId is PermalinkData.FallbackLink) {
+            _viewEvents.post(UserCodeShareViewEvents.ToastMessage(stringProvider.getString(R.string.not_a_valid_qr_code)))
+            return
+        }
+        _viewEvents.post(UserCodeShareViewEvents.ShowWaitingScreen)
+        viewModelScope.launch(Dispatchers.IO) {
+            when (linkedId) {
+                is PermalinkData.RoomLink -> TODO()
+                is PermalinkData.UserLink -> {
+                    var user = session.getUser(linkedId.userId) ?: awaitCallback<List<User>> {
+                        session.searchUsersDirectory(linkedId.userId, 10, emptySet(), it)
+                    }.firstOrNull { it.userId == linkedId.userId }
+                    // Create raw Uxid in case the user is not searchable
+                    ?: User(linkedId.userId, null, null)
+
+                    setState {
+                        copy(
+                                mode = UserCodeState.Mode.RESULT(user.toMatrixItem())
+                        )
+                    }
+                }
+                is PermalinkData.GroupLink -> TODO()
+                is PermalinkData.FallbackLink -> TODO()
+            }
+            _viewEvents.post(UserCodeShareViewEvents.HideWaitingScreen)
+        }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt
new file mode 100644
index 0000000000..3be882af3d
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/usercode/UserCodeState.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.usercode
+
+import com.airbnb.mvrx.MvRxState
+import org.matrix.android.sdk.api.util.MatrixItem
+
+data class UserCodeState(
+        val userId: String,
+        val matrixItem: MatrixItem? = null,
+        val shareLink: String? = null,
+        val mode: Mode = Mode.SHOW
+) : MvRxState {
+    sealed class Mode {
+        object SHOW : Mode()
+        object SCAN : Mode()
+        data class RESULT(val matrixItem: MatrixItem) : Mode()
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt
new file mode 100644
index 0000000000..2307640634
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/ActionItem.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.userdirectory
+
+import android.view.View
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.annotation.DrawableRes
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.epoxy.VectorEpoxyModel
+import im.vector.app.core.extensions.setTextOrHide
+
+@EpoxyModelClass(layout = R.layout.item_contact_action)
+abstract class ActionItem : VectorEpoxyModel<ActionItem.Holder>() {
+
+    @EpoxyAttribute var title: CharSequence? = null
+    @EpoxyAttribute @DrawableRes var actionIconRes: Int? = null
+    @EpoxyAttribute var clickAction: View.OnClickListener? = null
+
+    override fun bind(holder: Holder) {
+        super.bind(holder)
+        holder.view.setOnClickListener(clickAction)
+        // If name is empty, use userId as name and force it being centered
+        holder.actionTitleText.setTextOrHide(title)
+        if (actionIconRes != null) {
+            holder.actionTitleImageView.setImageResource(actionIconRes!!)
+        } else {
+            holder.actionTitleImageView.setImageDrawable(null)
+        }
+    }
+
+    class Holder : VectorEpoxyHolder() {
+        val actionTitleText by bind<TextView>(R.id.actionTitleText)
+        val actionTitleImageView by bind<ImageView>(R.id.actionIconImageView)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/ContactDetailItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/ContactDetailItem.kt
new file mode 100644
index 0000000000..ee96c34f45
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/ContactDetailItem.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.userdirectory
+
+import android.widget.TextView
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.ClickListener
+import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.epoxy.VectorEpoxyModel
+import im.vector.app.core.epoxy.onClick
+import im.vector.app.core.extensions.setTextOrHide
+
+@EpoxyModelClass(layout = R.layout.item_contact_detail)
+abstract class ContactDetailItem : VectorEpoxyModel<ContactDetailItem.Holder>() {
+
+    @EpoxyAttribute lateinit var threePid: String
+    @EpoxyAttribute var matrixId: String? = null
+    @EpoxyAttribute var clickListener: ClickListener? = null
+
+    override fun bind(holder: Holder) {
+        super.bind(holder)
+        holder.view.onClick(clickListener)
+        holder.nameView.text = threePid
+        holder.matrixIdView.setTextOrHide(matrixId)
+    }
+
+    class Holder : VectorEpoxyHolder() {
+        val nameView by bind<TextView>(R.id.contactDetailName)
+        val matrixIdView by bind<TextView>(R.id.contactDetailMatrixId)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/ContactItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/ContactItem.kt
new file mode 100644
index 0000000000..d9f424d961
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/ContactItem.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.userdirectory
+
+import android.widget.ImageView
+import android.widget.TextView
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.contacts.MappedContact
+import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.epoxy.VectorEpoxyModel
+import im.vector.app.features.home.AvatarRenderer
+
+@EpoxyModelClass(layout = R.layout.item_contact_main)
+abstract class ContactItem : VectorEpoxyModel<ContactItem.Holder>() {
+
+    @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
+    @EpoxyAttribute lateinit var mappedContact: MappedContact
+
+    override fun bind(holder: Holder) {
+        super.bind(holder)
+        // If name is empty, use userId as name and force it being centered
+        holder.nameView.text = mappedContact.displayName
+        avatarRenderer.render(mappedContact, holder.avatarImageView)
+    }
+
+    class Holder : VectorEpoxyHolder() {
+        val nameView by bind<TextView>(R.id.contactDisplayName)
+        val avatarImageView by bind<ImageView>(R.id.contactAvatar)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/DirectoryUsersController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/DirectoryUsersController.kt
deleted file mode 100644
index e68d9855dd..0000000000
--- a/vector/src/main/java/im/vector/app/features/userdirectory/DirectoryUsersController.kt
+++ /dev/null
@@ -1,139 +0,0 @@
-/*
- * Copyright (c) 2020 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.userdirectory
-
-import com.airbnb.epoxy.EpoxyController
-import com.airbnb.mvrx.Fail
-import com.airbnb.mvrx.Loading
-import com.airbnb.mvrx.Success
-import com.airbnb.mvrx.Uninitialized
-import im.vector.app.R
-import im.vector.app.core.epoxy.errorWithRetryItem
-import im.vector.app.core.epoxy.loadingItem
-import im.vector.app.core.epoxy.noResultItem
-import im.vector.app.core.error.ErrorFormatter
-import im.vector.app.core.resources.StringProvider
-import im.vector.app.features.home.AvatarRenderer
-import org.matrix.android.sdk.api.MatrixPatterns
-import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.api.session.user.model.User
-import org.matrix.android.sdk.api.util.toMatrixItem
-import javax.inject.Inject
-
-class DirectoryUsersController @Inject constructor(private val session: Session,
-                                                   private val avatarRenderer: AvatarRenderer,
-                                                   private val stringProvider: StringProvider,
-                                                   private val errorFormatter: ErrorFormatter) : EpoxyController() {
-
-    private var state: UserDirectoryViewState? = null
-
-    var callback: Callback? = null
-
-    init {
-        requestModelBuild()
-    }
-
-    fun setData(state: UserDirectoryViewState) {
-        this.state = state
-        requestModelBuild()
-    }
-
-    override fun buildModels() {
-        val currentState = state ?: return
-        val hasSearch = currentState.directorySearchTerm.isNotBlank()
-        when (val asyncUsers = currentState.directoryUsers) {
-            is Uninitialized -> renderEmptyState(false)
-            is Loading       -> renderLoading()
-            is Success       -> renderSuccess(
-                    computeUsersList(asyncUsers(), currentState.directorySearchTerm),
-                    currentState.getSelectedMatrixId(),
-                    hasSearch
-            )
-            is Fail          -> renderFailure(asyncUsers.error)
-        }
-    }
-
-    /**
-     * Eventually add the searched terms, if it is a userId, and if not already present in the result
-     */
-    private fun computeUsersList(directoryUsers: List<User>, searchTerms: String): List<User> {
-        return directoryUsers +
-                searchTerms
-                        .takeIf { terms -> MatrixPatterns.isUserId(terms) && !directoryUsers.any { it.userId == terms } }
-                        ?.let { listOf(User(it)) }
-                        .orEmpty()
-    }
-
-    private fun renderLoading() {
-        loadingItem {
-            id("loading")
-        }
-    }
-
-    private fun renderFailure(failure: Throwable) {
-        errorWithRetryItem {
-            id("error")
-            text(errorFormatter.toHumanReadable(failure))
-            listener { callback?.retryDirectoryUsersRequest() }
-        }
-    }
-
-    private fun renderSuccess(users: List<User>,
-                              selectedUsers: List<String>,
-                              hasSearch: Boolean) {
-        if (users.isEmpty()) {
-            renderEmptyState(hasSearch)
-        } else {
-            renderUsers(users, selectedUsers)
-        }
-    }
-
-    private fun renderUsers(users: List<User>, selectedUsers: List<String>) {
-        for (user in users) {
-            if (user.userId == session.myUserId) {
-                continue
-            }
-            val isSelected = selectedUsers.contains(user.userId)
-            userDirectoryUserItem {
-                id(user.userId)
-                selected(isSelected)
-                matrixItem(user.toMatrixItem())
-                avatarRenderer(avatarRenderer)
-                clickListener { _ ->
-                    callback?.onItemClick(user)
-                }
-            }
-        }
-    }
-
-    private fun renderEmptyState(hasSearch: Boolean) {
-        val noResultRes = if (hasSearch) {
-            R.string.no_result_placeholder
-        } else {
-            R.string.direct_room_start_search
-        }
-        noResultItem {
-            id("noResult")
-            text(stringProvider.getString(noResultRes))
-        }
-    }
-
-    interface Callback {
-        fun onItemClick(user: User)
-        fun retryDirectoryUsersRequest()
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersController.kt
deleted file mode 100644
index 4fbb9bbb41..0000000000
--- a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersController.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (c) 2020 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.userdirectory
-
-import com.airbnb.epoxy.EpoxyModel
-import com.airbnb.epoxy.paging.PagedListEpoxyController
-import com.airbnb.mvrx.Async
-import com.airbnb.mvrx.Incomplete
-import com.airbnb.mvrx.Uninitialized
-import im.vector.app.R
-import im.vector.app.core.epoxy.EmptyItem_
-import im.vector.app.core.epoxy.loadingItem
-import im.vector.app.core.epoxy.noResultItem
-import im.vector.app.core.resources.StringProvider
-import im.vector.app.core.utils.createUIHandler
-import im.vector.app.features.home.AvatarRenderer
-import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.api.session.user.model.User
-import org.matrix.android.sdk.api.util.toMatrixItem
-import javax.inject.Inject
-
-class KnownUsersController @Inject constructor(private val session: Session,
-                                               private val avatarRenderer: AvatarRenderer,
-                                               private val stringProvider: StringProvider) : PagedListEpoxyController<User>(
-        modelBuildingHandler = createUIHandler()
-) {
-
-    private var selectedUsers: List<String> = emptyList()
-    private var users: Async<List<User>> = Uninitialized
-    private var isFiltering: Boolean = false
-
-    var callback: Callback? = null
-
-    init {
-        requestModelBuild()
-    }
-
-    fun setData(state: UserDirectoryViewState) {
-        this.isFiltering = !state.filterKnownUsersValue.isEmpty()
-        val newSelection = state.getSelectedMatrixId()
-        this.users = state.knownUsers
-        if (newSelection != selectedUsers) {
-            this.selectedUsers = newSelection
-            requestForcedModelBuild()
-        }
-        submitList(state.knownUsers())
-    }
-
-    override fun buildItemModel(currentPosition: Int, item: User?): EpoxyModel<*> {
-        return if (item == null) {
-            EmptyItem_().id(currentPosition)
-        } else {
-            val isSelected = selectedUsers.contains(item.userId)
-            UserDirectoryUserItem_()
-                    .id(item.userId)
-                    .selected(isSelected)
-                    .matrixItem(item.toMatrixItem())
-                    .avatarRenderer(avatarRenderer)
-                    .clickListener { _ ->
-                        callback?.onItemClick(item)
-                    }
-        }
-    }
-
-    override fun addModels(models: List<EpoxyModel<*>>) {
-        if (users is Incomplete) {
-            renderLoading()
-        } else if (models.isEmpty()) {
-            renderEmptyState()
-        } else {
-            var lastFirstLetter: String? = null
-            for (model in models) {
-                if (model is UserDirectoryUserItem) {
-                    if (model.matrixItem.id == session.myUserId) continue
-                    val currentFirstLetter = model.matrixItem.firstLetterOfDisplayName()
-                    val showLetter = !isFiltering && currentFirstLetter.isNotEmpty() && lastFirstLetter != currentFirstLetter
-                    lastFirstLetter = currentFirstLetter
-
-                    UserDirectoryLetterHeaderItem_()
-                            .id(currentFirstLetter)
-                            .letter(currentFirstLetter)
-                            .addIf(showLetter, this)
-
-                    model.addTo(this)
-                } else {
-                    continue
-                }
-            }
-        }
-    }
-
-    private fun renderLoading() {
-        loadingItem {
-            id("loading")
-        }
-    }
-
-    private fun renderEmptyState() {
-        noResultItem {
-            id("noResult")
-            text(stringProvider.getString(R.string.direct_room_no_known_users))
-        }
-    }
-
-    interface Callback {
-        fun onItemClick(user: User)
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt
deleted file mode 100644
index 70ea9141e7..0000000000
--- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryFragment.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (c) 2020 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.userdirectory
-
-import android.os.Bundle
-import android.view.View
-import com.airbnb.mvrx.activityViewModel
-import com.airbnb.mvrx.withState
-import com.jakewharton.rxbinding3.widget.textChanges
-import im.vector.app.R
-import im.vector.app.core.extensions.cleanup
-import im.vector.app.core.extensions.configureWith
-import im.vector.app.core.extensions.hideKeyboard
-import im.vector.app.core.extensions.setupAsSearch
-import im.vector.app.core.extensions.showKeyboard
-import im.vector.app.core.platform.VectorBaseFragment
-import kotlinx.android.synthetic.main.fragment_user_directory.*
-import org.matrix.android.sdk.api.session.user.model.User
-import javax.inject.Inject
-
-class UserDirectoryFragment @Inject constructor(
-        private val directRoomController: DirectoryUsersController
-) : VectorBaseFragment(), DirectoryUsersController.Callback {
-
-    override fun getLayoutResId() = R.layout.fragment_user_directory
-    private val viewModel: UserDirectoryViewModel by activityViewModel()
-
-    private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
-
-    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
-        super.onViewCreated(view, savedInstanceState)
-        sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
-        setupRecyclerView()
-        setupSearchByMatrixIdView()
-        setupCloseView()
-    }
-
-    override fun onDestroyView() {
-        userDirectoryRecyclerView.cleanup()
-        directRoomController.callback = null
-        super.onDestroyView()
-    }
-
-    private fun setupRecyclerView() {
-        directRoomController.callback = this
-        userDirectoryRecyclerView.configureWith(directRoomController)
-    }
-
-    private fun setupSearchByMatrixIdView() {
-        userDirectorySearchById.setupAsSearch(searchIconRes = 0)
-        userDirectorySearchById
-                .textChanges()
-                .subscribe {
-                    viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(it.toString()))
-                }
-                .disposeOnDestroyView()
-        userDirectorySearchById.showKeyboard(andRequestFocus = true)
-    }
-
-    private fun setupCloseView() {
-        userDirectoryClose.debouncedClicks {
-            sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
-        }
-    }
-
-    override fun invalidate() = withState(viewModel) {
-        directRoomController.setData(it)
-    }
-
-    override fun onItemClick(user: User) {
-        view?.hideKeyboard()
-        viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user)))
-        sharedActionViewModel.post(UserDirectorySharedAction.GoBack)
-    }
-
-    override fun retryDirectoryUsersRequest() {
-        val currentSearch = userDirectorySearchById.text.toString()
-        viewModel.handle(UserDirectoryAction.SearchDirectoryUsers(currentSearch))
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt
deleted file mode 100644
index 0a24b85ce2..0000000000
--- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewModel.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (c) 2020 New Vector Ltd
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package im.vector.app.features.userdirectory
-
-import androidx.fragment.app.FragmentActivity
-import arrow.core.Option
-import com.airbnb.mvrx.ActivityViewModelContext
-import com.airbnb.mvrx.FragmentViewModelContext
-import com.airbnb.mvrx.MvRxViewModelFactory
-import com.airbnb.mvrx.ViewModelContext
-import com.jakewharton.rxrelay2.BehaviorRelay
-import com.squareup.inject.assisted.Assisted
-import com.squareup.inject.assisted.AssistedInject
-import im.vector.app.core.extensions.exhaustive
-import im.vector.app.core.extensions.toggle
-import im.vector.app.core.platform.VectorViewModel
-import im.vector.app.features.createdirect.CreateDirectRoomActivity
-import im.vector.app.features.invite.InviteUsersToRoomActivity
-import io.reactivex.Single
-import io.reactivex.android.schedulers.AndroidSchedulers
-import org.matrix.android.sdk.api.session.Session
-import org.matrix.android.sdk.api.util.toMatrixItem
-import org.matrix.android.sdk.rx.rx
-import java.util.concurrent.TimeUnit
-
-private typealias KnowUsersFilter = String
-private typealias DirectoryUsersSearch = String
-
-class UserDirectoryViewModel @AssistedInject constructor(@Assisted
-                                                         initialState: UserDirectoryViewState,
-                                                         private val session: Session)
-    : VectorViewModel<UserDirectoryViewState, UserDirectoryAction, UserDirectoryViewEvents>(initialState) {
-
-    @AssistedInject.Factory
-    interface Factory {
-        fun create(initialState: UserDirectoryViewState): UserDirectoryViewModel
-    }
-
-    private val knownUsersFilter = BehaviorRelay.createDefault<Option<KnowUsersFilter>>(Option.empty())
-    private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
-
-    companion object : MvRxViewModelFactory<UserDirectoryViewModel, UserDirectoryViewState> {
-
-        override fun create(viewModelContext: ViewModelContext, state: UserDirectoryViewState): UserDirectoryViewModel? {
-            return when (viewModelContext) {
-                is FragmentViewModelContext -> (viewModelContext.fragment() as KnownUsersFragment).userDirectoryViewModelFactory.create(state)
-                is ActivityViewModelContext -> {
-                    when (viewModelContext.activity<FragmentActivity>()) {
-                        is CreateDirectRoomActivity  -> viewModelContext.activity<CreateDirectRoomActivity>().userDirectoryViewModelFactory.create(state)
-                        is InviteUsersToRoomActivity -> viewModelContext.activity<InviteUsersToRoomActivity>().userDirectoryViewModelFactory.create(state)
-                        else                         -> error("Wrong activity or fragment")
-                    }
-                }
-                else                        -> error("Wrong activity or fragment")
-            }
-        }
-    }
-
-    init {
-        observeKnownUsers()
-        observeDirectoryUsers()
-    }
-
-    override fun handle(action: UserDirectoryAction) {
-        when (action) {
-            is UserDirectoryAction.FilterKnownUsers      -> knownUsersFilter.accept(Option.just(action.value))
-            is UserDirectoryAction.ClearFilterKnownUsers -> knownUsersFilter.accept(Option.empty())
-            is UserDirectoryAction.SearchDirectoryUsers  -> directoryUsersSearch.accept(action.value)
-            is UserDirectoryAction.SelectPendingInvitee  -> handleSelectUser(action)
-            is UserDirectoryAction.RemovePendingInvitee  -> handleRemoveSelectedUser(action)
-        }.exhaustive
-    }
-
-    private fun handleRemoveSelectedUser(action: UserDirectoryAction.RemovePendingInvitee) = withState { state ->
-        val selectedUsers = state.pendingInvitees.minus(action.pendingInvitee)
-        setState {
-            copy(
-                    pendingInvitees = selectedUsers,
-                    existingDmRoomId = getExistingDmRoomId(selectedUsers)
-            )
-        }
-    }
-
-    private fun handleSelectUser(action: UserDirectoryAction.SelectPendingInvitee) = withState { state ->
-        // Reset the filter asap
-        directoryUsersSearch.accept("")
-        val selectedUsers = state.pendingInvitees.toggle(action.pendingInvitee)
-        setState {
-            copy(
-                    pendingInvitees = selectedUsers,
-                    existingDmRoomId = getExistingDmRoomId(selectedUsers)
-            )
-        }
-    }
-
-    private fun getExistingDmRoomId(selectedUsers: Set<PendingInvitee>): String? {
-        return selectedUsers
-                .takeIf { it.size == 1 }
-                ?.filterIsInstance(PendingInvitee.UserPendingInvitee::class.java)
-                ?.firstOrNull()
-                ?.let { invitee -> session.getExistingDirectRoomWithUser(invitee.user.userId) }
-    }
-
-    private fun observeDirectoryUsers() = withState { state ->
-        directoryUsersSearch
-                .debounce(300, TimeUnit.MILLISECONDS)
-                .switchMapSingle { search ->
-                    val stream = if (search.isBlank()) {
-                        Single.just(emptyList())
-                    } else {
-                        session.rx()
-                                .searchUsersDirectory(search, 50, state.excludedUserIds ?: emptySet())
-                                .map { users ->
-                                    users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
-                                }
-                    }
-                    stream.toAsync {
-                        copy(directoryUsers = it, directorySearchTerm = search)
-                    }
-                }
-                .subscribe()
-                .disposeOnClear()
-    }
-
-    private fun observeKnownUsers() = withState { state ->
-        knownUsersFilter
-                .throttleLast(300, TimeUnit.MILLISECONDS)
-                .observeOn(AndroidSchedulers.mainThread())
-                .switchMap {
-                    session.rx().livePagedUsers(it.orNull(), state.excludedUserIds)
-                }
-                .execute { async ->
-                    copy(
-                            knownUsers = async,
-                            filterKnownUsersValue = knownUsersFilter.value ?: Option.empty()
-                    )
-                }
-    }
-}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryAction.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt
similarity index 71%
rename from vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryAction.kt
rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt
index f4f3fb8cd4..0c2c4b1f4b 100644
--- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryAction.kt
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListAction.kt
@@ -18,10 +18,10 @@ package im.vector.app.features.userdirectory
 
 import im.vector.app.core.platform.VectorViewModelAction
 
-sealed class UserDirectoryAction : VectorViewModelAction {
-    data class FilterKnownUsers(val value: String) : UserDirectoryAction()
-    data class SearchDirectoryUsers(val value: String) : UserDirectoryAction()
-    object ClearFilterKnownUsers : UserDirectoryAction()
-    data class SelectPendingInvitee(val pendingInvitee: PendingInvitee) : UserDirectoryAction()
-    data class RemovePendingInvitee(val pendingInvitee: PendingInvitee) : UserDirectoryAction()
+sealed class UserListAction : VectorViewModelAction {
+    data class SearchUsers(val value: String) : UserListAction()
+    object ClearSearchUsers : UserListAction()
+    data class SelectPendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction()
+    data class RemovePendingInvitee(val pendingInvitee: PendingInvitee) : UserListAction()
+    object ComputeMatrixToLinkForSharing : UserListAction()
 }
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt
new file mode 100644
index 0000000000..6cd76401fd
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListController.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.userdirectory
+
+import android.view.View
+import com.airbnb.epoxy.EpoxyController
+import com.airbnb.mvrx.Fail
+import com.airbnb.mvrx.Loading
+import com.airbnb.mvrx.Success
+import com.airbnb.mvrx.Uninitialized
+import im.vector.app.R
+import im.vector.app.core.epoxy.errorWithRetryItem
+import im.vector.app.core.epoxy.loadingItem
+import im.vector.app.core.epoxy.noResultItem
+import im.vector.app.core.error.ErrorFormatter
+import im.vector.app.core.resources.StringProvider
+import im.vector.app.features.home.AvatarRenderer
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.identity.ThreePid
+import org.matrix.android.sdk.api.session.user.model.User
+import org.matrix.android.sdk.api.util.toMatrixItem
+import javax.inject.Inject
+
+class UserListController @Inject constructor(private val session: Session,
+                                             private val avatarRenderer: AvatarRenderer,
+                                             private val stringProvider: StringProvider,
+                                             private val errorFormatter: ErrorFormatter) : EpoxyController() {
+
+    private var state: UserListViewState? = null
+
+    var callback: Callback? = null
+
+    fun setData(state: UserListViewState) {
+        this.state = state
+        requestModelBuild()
+    }
+
+    override fun buildModels() {
+        val currentState = state ?: return
+
+        // Build generic items
+        if (currentState.searchTerm.isBlank()) {
+            // For now we remove this option if in invite to existing room flow (and not create DM)
+            if (currentState.pendingInvitees.isEmpty()
+                    // For now we remove this option if in invite to existing room flow (and not create DM)
+                    && currentState.existingRoomId == null) {
+                actionItem {
+                    id(R.drawable.ic_invite_people)
+                    title(stringProvider.getString(R.string.invite_friends))
+                    actionIconRes(R.drawable.ic_invite_people)
+                    clickAction(View.OnClickListener {
+                        callback?.onInviteFriendClick()
+                    })
+                }
+            }
+            actionItem {
+                id(R.drawable.ic_book)
+                title(stringProvider.getString(R.string.contacts_book_title))
+                actionIconRes(R.drawable.ic_book)
+                clickAction(View.OnClickListener {
+                    callback?.onContactBookClick()
+                })
+            }
+            if (currentState.pendingInvitees.isEmpty()
+                    // For now we remove this option if in invite to existing room flow (and not create DM)
+                    && currentState.existingRoomId == null) {
+                actionItem {
+                    id(R.drawable.ic_qr_code_add)
+                    title(stringProvider.getString(R.string.qr_code))
+                    actionIconRes(R.drawable.ic_qr_code_add)
+                    clickAction(View.OnClickListener {
+                        callback?.onUseQRCode()
+                    })
+                }
+            }
+        }
+
+        when (currentState.knownUsers) {
+            is Uninitialized -> renderEmptyState()
+            is Loading       -> renderLoading()
+            is Fail          -> renderFailure(currentState.knownUsers.error)
+            is Success       -> buildKnownUsers(currentState, currentState.getSelectedMatrixId())
+        }
+
+        when (val asyncUsers = currentState.directoryUsers) {
+            is Uninitialized -> {
+            }
+            is Loading       -> renderLoading()
+            is Fail          -> renderFailure(asyncUsers.error)
+            is Success       -> buildDirectoryUsers(
+                    asyncUsers(),
+                    currentState.getSelectedMatrixId(),
+                    currentState.searchTerm,
+                    // to avoid showing twice same user in known and suggestions
+                    currentState.knownUsers.invoke()?.map { it.userId } ?: emptyList()
+            )
+        }
+    }
+
+    private fun buildKnownUsers(currentState: UserListViewState, selectedUsers: List<String>) {
+        currentState.knownUsers()?.let { userList ->
+            userListHeaderItem {
+                id("known_header")
+                header(stringProvider.getString(R.string.direct_room_user_list_known_title))
+            }
+
+            if (userList.isEmpty()) {
+                renderEmptyState()
+                return
+            }
+            userList.forEach { item ->
+                val isSelected = selectedUsers.contains(item.userId)
+                userDirectoryUserItem {
+                    id(item.userId)
+                    selected(isSelected)
+                    matrixItem(item.toMatrixItem())
+                    avatarRenderer(avatarRenderer)
+                    clickListener { _ ->
+                        callback?.onItemClick(item)
+                    }
+                }
+            }
+        }
+    }
+
+    private fun buildDirectoryUsers(directoryUsers: List<User>, selectedUsers: List<String>, searchTerms: String, ignoreIds: List<String>) {
+        val toDisplay = directoryUsers.filter { !ignoreIds.contains(it.userId) }
+        if (toDisplay.isEmpty() && searchTerms.isBlank()) {
+            return
+        }
+        userListHeaderItem {
+            id("suggestions")
+            header(stringProvider.getString(R.string.direct_room_user_list_suggestions_title))
+        }
+        if (toDisplay.isEmpty()) {
+            renderEmptyState()
+        } else {
+            toDisplay.forEach { user ->
+                if (user.userId != session.myUserId) {
+                    val isSelected = selectedUsers.contains(user.userId)
+                    userDirectoryUserItem {
+                        id(user.userId)
+                        selected(isSelected)
+                        matrixItem(user.toMatrixItem())
+                        avatarRenderer(avatarRenderer)
+                        clickListener { _ ->
+                            callback?.onItemClick(user)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private fun renderLoading() {
+        loadingItem {
+            id("loading")
+        }
+    }
+
+    private fun renderEmptyState() {
+        noResultItem {
+            id("noResult")
+            text(stringProvider.getString(R.string.no_result_placeholder))
+        }
+    }
+
+    private fun renderFailure(failure: Throwable) {
+        errorWithRetryItem {
+            id("error")
+            text(errorFormatter.toHumanReadable(failure))
+        }
+    }
+
+    interface Callback {
+        fun onInviteFriendClick()
+        fun onContactBookClick()
+        fun onUseQRCode()
+        fun onItemClick(user: User)
+        fun onMatrixIdClick(matrixId: String)
+        fun onThreePidClick(threePid: ThreePid)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt
similarity index 57%
rename from vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt
rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt
index ec684e8eea..4af16772b8 100644
--- a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragment.kt
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragment.kt
@@ -36,53 +36,64 @@ import im.vector.app.core.extensions.hideKeyboard
 import im.vector.app.core.extensions.setupAsSearch
 import im.vector.app.core.platform.VectorBaseFragment
 import im.vector.app.core.utils.DimensionConverter
+import im.vector.app.core.utils.startSharePlainTextIntent
 import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel
-import kotlinx.android.synthetic.main.fragment_known_users.*
+import kotlinx.android.synthetic.main.fragment_user_list.*
+import org.matrix.android.sdk.api.session.identity.ThreePid
 import org.matrix.android.sdk.api.session.user.model.User
 import javax.inject.Inject
 
-class KnownUsersFragment @Inject constructor(
-        val userDirectoryViewModelFactory: UserDirectoryViewModel.Factory,
-        private val knownUsersController: KnownUsersController,
+class UserListFragment @Inject constructor(
+        private val userListController: UserListController,
         private val dimensionConverter: DimensionConverter,
         val homeServerCapabilitiesViewModelFactory: HomeServerCapabilitiesViewModel.Factory
-) : VectorBaseFragment(), KnownUsersController.Callback {
+) : VectorBaseFragment(), UserListController.Callback {
 
-    private val args: KnownUsersFragmentArgs by args()
+    private val args: UserListFragmentArgs by args()
+    private val viewModel: UserListViewModel by activityViewModel()
+    private val homeServerCapabilitiesViewModel: HomeServerCapabilitiesViewModel by fragmentViewModel()
+    private lateinit var sharedActionViewModel: UserListSharedActionViewModel
 
-    override fun getLayoutResId() = R.layout.fragment_known_users
+    override fun getLayoutResId() = R.layout.fragment_user_list
 
     override fun getMenuRes() = args.menuResId
 
-    private val viewModel: UserDirectoryViewModel by activityViewModel()
-    private val homeServerCapabilitiesViewModel: HomeServerCapabilitiesViewModel by fragmentViewModel()
-
-    private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel
-
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
+        sharedActionViewModel = activityViewModelProvider.get(UserListSharedActionViewModel::class.java)
+        userListTitle.text = args.title
+        vectorBaseActivity.setSupportActionBar(userListToolbar)
 
-        knownUsersTitle.text = args.title
-        vectorBaseActivity.setSupportActionBar(knownUsersToolbar)
         setupRecyclerView()
-        setupFilterView()
-        setupAddByMatrixIdView()
-        setupAddFromPhoneBookView()
+        setupSearchView()
         setupCloseView()
 
         homeServerCapabilitiesViewModel.subscribe {
-            knownUsersE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault
+            userListE2EbyDefaultDisabled.isVisible = !it.isE2EByDefault
         }
 
-        viewModel.selectSubscribe(this, UserDirectoryViewState::pendingInvitees) {
+        viewModel.selectSubscribe(this, UserListViewState::pendingInvitees) {
             renderSelectedUsers(it)
         }
+
+        viewModel.observeViewEvents {
+            when (it) {
+                is UserListViewEvents.OpenShareMatrixToLing -> {
+                    val text = getString(R.string.invite_friends_text, it.link)
+                    startSharePlainTextIntent(
+                            fragment = this,
+                            activityResultLauncher = null,
+                            chooserTitle = getString(R.string.invite_friends),
+                            text = text,
+                            extraTitle = getString(R.string.invite_friends_rich_title)
+                    )
+                }
+            }
+        }
     }
 
     override fun onDestroyView() {
-        knownUsersController.callback = null
-        knownUsersRecyclerView.cleanup()
+        recyclerView.cleanup()
         super.onDestroyView()
     }
 
@@ -91,69 +102,52 @@ class KnownUsersFragment @Inject constructor(
             val showMenuItem = it.pendingInvitees.isNotEmpty()
             menu.forEach { menuItem ->
                 menuItem.isVisible = showMenuItem
-                if (args.isCreatingRoom) {
-                    menuItem.setTitle(if (it.existingDmRoomId != null) R.string.action_open else R.string.create_room_action_create)
-                }
             }
         }
         super.onPrepareOptionsMenu(menu)
     }
 
     override fun onOptionsItemSelected(item: MenuItem): Boolean = withState(viewModel) {
-        sharedActionViewModel.post(UserDirectorySharedAction.OnMenuItemSelected(
-                item.itemId,
-                it.pendingInvitees,
-                it.existingDmRoomId
-        ))
+        sharedActionViewModel.post(UserListSharedAction.OnMenuItemSelected(item.itemId, it.pendingInvitees))
         return@withState true
     }
 
-    private fun setupAddByMatrixIdView() {
-        addByMatrixId.debouncedClicks {
-            sharedActionViewModel.post(UserDirectorySharedAction.OpenUsersDirectory)
-        }
-    }
-
-    private fun setupAddFromPhoneBookView() {
-        addFromPhoneBook.debouncedClicks {
-            // TODO handle Permission first
-            sharedActionViewModel.post(UserDirectorySharedAction.OpenPhoneBook)
-        }
-    }
-
     private fun setupRecyclerView() {
-        knownUsersController.callback = this
+        userListController.callback = this
         // Don't activate animation as we might have way to much item animation when filtering
-        knownUsersRecyclerView.configureWith(knownUsersController, disableItemAnimation = true)
+        recyclerView.configureWith(userListController, disableItemAnimation = true)
     }
 
-    private fun setupFilterView() {
-        knownUsersFilter
+    private fun setupSearchView() {
+        withState(viewModel) {
+            userListSearch.hint = getString(R.string.user_directory_search_hint, it.myUserId)
+        }
+        userListSearch
                 .textChanges()
-                .startWith(knownUsersFilter.text)
+                .startWith(userListSearch.text)
                 .subscribe { text ->
-                    val filterValue = text.trim()
-                    val action = if (filterValue.isBlank()) {
-                        UserDirectoryAction.ClearFilterKnownUsers
+                    val searchValue = text.trim()
+                    val action = if (searchValue.isBlank()) {
+                        UserListAction.ClearSearchUsers
                     } else {
-                        UserDirectoryAction.FilterKnownUsers(filterValue.toString())
+                        UserListAction.SearchUsers(searchValue.toString())
                     }
                     viewModel.handle(action)
                 }
                 .disposeOnDestroyView()
 
-        knownUsersFilter.setupAsSearch()
-        knownUsersFilter.requestFocus()
+        userListSearch.setupAsSearch()
+        userListSearch.requestFocus()
     }
 
     private fun setupCloseView() {
-        knownUsersClose.debouncedClicks {
+        userListClose.debouncedClicks {
             requireActivity().finish()
         }
     }
 
     override fun invalidate() = withState(viewModel) {
-        knownUsersController.setData(it)
+        userListController.setData(it)
     }
 
     private fun renderSelectedUsers(invitees: Set<PendingInvitee>) {
@@ -183,12 +177,35 @@ class KnownUsersFragment @Inject constructor(
         chip.isCloseIconVisible = true
         chipGroup.addView(chip)
         chip.setOnCloseIconClickListener {
-            viewModel.handle(UserDirectoryAction.RemovePendingInvitee(pendingInvitee))
+            viewModel.handle(UserListAction.RemovePendingInvitee(pendingInvitee))
         }
     }
 
+    override fun onInviteFriendClick() {
+        viewModel.handle(UserListAction.ComputeMatrixToLinkForSharing)
+    }
+
+    override fun onContactBookClick() {
+        sharedActionViewModel.post(UserListSharedAction.OpenPhoneBook)
+    }
+
     override fun onItemClick(user: User) {
         view?.hideKeyboard()
-        viewModel.handle(UserDirectoryAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user)))
+        viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(user)))
+    }
+
+    override fun onMatrixIdClick(matrixId: String) {
+        view?.hideKeyboard()
+        viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.UserPendingInvitee(User(matrixId))))
+    }
+
+    override fun onThreePidClick(threePid: ThreePid) {
+        view?.hideKeyboard()
+        viewModel.handle(UserListAction.SelectPendingInvitee(PendingInvitee.ThreePidPendingInvitee(threePid)))
+    }
+
+    override fun onUseQRCode() {
+        view?.hideKeyboard()
+        sharedActionViewModel.post(UserListSharedAction.AddByQrCode)
     }
 }
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragmentArgs.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt
similarity index 91%
rename from vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragmentArgs.kt
rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt
index c20aedb803..041f29a77a 100644
--- a/vector/src/main/java/im/vector/app/features/userdirectory/KnownUsersFragmentArgs.kt
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListFragmentArgs.kt
@@ -20,9 +20,9 @@ import android.os.Parcelable
 import kotlinx.android.parcel.Parcelize
 
 @Parcelize
-data class KnownUsersFragmentArgs(
+data class UserListFragmentArgs(
         val title: String,
         val menuResId: Int,
         val excludedUserIds: Set<String>? = null,
-        val isCreatingRoom: Boolean = false
+        val existingRoomId: String? = null
 ) : Parcelable
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListHeaderItem.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListHeaderItem.kt
new file mode 100644
index 0000000000..82fa4a4d6f
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListHeaderItem.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.userdirectory
+
+import android.widget.TextView
+import com.airbnb.epoxy.EpoxyAttribute
+import com.airbnb.epoxy.EpoxyModelClass
+import im.vector.app.R
+import im.vector.app.core.epoxy.VectorEpoxyHolder
+import im.vector.app.core.epoxy.VectorEpoxyModel
+
+@EpoxyModelClass(layout = R.layout.item_user_list_header)
+abstract class UserListHeaderItem : VectorEpoxyModel<UserListHeaderItem.Holder>() {
+
+    @EpoxyAttribute var header: String = ""
+
+    override fun bind(holder: Holder) {
+        super.bind(holder)
+        holder.headerTextView.text = header
+    }
+
+    class Holder : VectorEpoxyHolder() {
+        val headerTextView by bind<TextView>(R.id.userListHeaderView)
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedAction.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt
similarity index 59%
rename from vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedAction.kt
rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt
index 14daa67f25..b2cdee3e63 100644
--- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedAction.kt
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedAction.kt
@@ -18,12 +18,10 @@ package im.vector.app.features.userdirectory
 
 import im.vector.app.core.platform.VectorSharedAction
 
-sealed class UserDirectorySharedAction : VectorSharedAction {
-    object OpenUsersDirectory : UserDirectorySharedAction()
-    object OpenPhoneBook : UserDirectorySharedAction()
-    object Close : UserDirectorySharedAction()
-    object GoBack : UserDirectorySharedAction()
-    data class OnMenuItemSelected(val itemId: Int,
-                                  val invitees: Set<PendingInvitee>,
-                                  val existingDmRoomId: String?) : UserDirectorySharedAction()
+sealed class UserListSharedAction : VectorSharedAction {
+    object Close : UserListSharedAction()
+    object GoBack : UserListSharedAction()
+    data class OnMenuItemSelected(val itemId: Int, val invitees: Set<PendingInvitee>) : UserListSharedAction()
+    object OpenPhoneBook : UserListSharedAction()
+    object AddByQrCode : UserListSharedAction()
 }
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedActionViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedActionViewModel.kt
similarity index 85%
rename from vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedActionViewModel.kt
rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedActionViewModel.kt
index b63682e57a..05ebc73cff 100644
--- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectorySharedActionViewModel.kt
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListSharedActionViewModel.kt
@@ -19,4 +19,4 @@ package im.vector.app.features.userdirectory
 import im.vector.app.core.platform.VectorSharedActionViewModel
 import javax.inject.Inject
 
-class UserDirectorySharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<UserDirectorySharedAction>()
+class UserListSharedActionViewModel @Inject constructor() : VectorSharedActionViewModel<UserListSharedAction>()
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewEvents.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt
similarity index 85%
rename from vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewEvents.kt
rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt
index bfbdc657ef..95c6729fad 100644
--- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewEvents.kt
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewEvents.kt
@@ -21,4 +21,6 @@ import im.vector.app.core.platform.VectorViewEvents
 /**
  * Transient events for invite users to room screen
  */
-sealed class UserDirectoryViewEvents : VectorViewEvents
+sealed class UserListViewEvents : VectorViewEvents {
+    data class OpenShareMatrixToLing(val link: String) : UserListViewEvents()
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt
new file mode 100644
index 0000000000..1011a3e28a
--- /dev/null
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewModel.kt
@@ -0,0 +1,204 @@
+/*
+ * Copyright (c) 2020 New Vector Ltd
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package im.vector.app.features.userdirectory
+
+import com.airbnb.mvrx.ActivityViewModelContext
+import com.airbnb.mvrx.FragmentViewModelContext
+import com.airbnb.mvrx.MvRxViewModelFactory
+import com.airbnb.mvrx.ViewModelContext
+import com.jakewharton.rxrelay2.BehaviorRelay
+import com.squareup.inject.assisted.Assisted
+import com.squareup.inject.assisted.AssistedInject
+import im.vector.app.core.extensions.exhaustive
+import im.vector.app.core.extensions.toggle
+import im.vector.app.core.platform.VectorViewModel
+import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.disposables.Disposable
+import org.matrix.android.sdk.api.MatrixPatterns
+import org.matrix.android.sdk.api.session.Session
+import org.matrix.android.sdk.api.session.profile.ProfileService
+import org.matrix.android.sdk.api.session.user.model.User
+import org.matrix.android.sdk.api.util.toMatrixItem
+import org.matrix.android.sdk.rx.rx
+import java.util.concurrent.TimeUnit
+
+private typealias KnownUsersSearch = String
+private typealias DirectoryUsersSearch = String
+
+class UserListViewModel @AssistedInject constructor(@Assisted initialState: UserListViewState,
+                                                    @Assisted args: UserListFragmentArgs,
+                                                    private val session: Session)
+    : VectorViewModel<UserListViewState, UserListAction, UserListViewEvents>(initialState) {
+
+    private val knownUsersSearch = BehaviorRelay.create<KnownUsersSearch>()
+    private val directoryUsersSearch = BehaviorRelay.create<DirectoryUsersSearch>()
+
+    private var currentUserSearchDisposable: Disposable? = null
+
+    @AssistedInject.Factory
+    interface Factory {
+        fun create(initialState: UserListViewState, args: UserListFragmentArgs): UserListViewModel
+    }
+
+    companion object : MvRxViewModelFactory<UserListViewModel, UserListViewState> {
+
+        private val USER_NOT_FOUND_MAP = emptyMap<String, Any>()
+        private val USER_NOT_FOUND = User("")
+
+        override fun create(viewModelContext: ViewModelContext, state: UserListViewState): UserListViewModel? {
+            val factory = when (viewModelContext) {
+                is FragmentViewModelContext -> viewModelContext.fragment as? Factory
+                is ActivityViewModelContext -> viewModelContext.activity as? Factory
+            }
+            val args = viewModelContext.args<UserListFragmentArgs>()
+            return factory?.create(state, args) ?: error("You should let your activity/fragment implements Factory interface")
+        }
+    }
+
+    init {
+        setState {
+            copy(
+                    myUserId = session.myUserId,
+                    existingRoomId = args.existingRoomId
+            )
+        }
+        observeUsers()
+    }
+
+    override fun handle(action: UserListAction) {
+        when (action) {
+            is UserListAction.SearchUsers                -> handleSearchUsers(action.value)
+            is UserListAction.ClearSearchUsers           -> handleClearSearchUsers()
+            is UserListAction.SelectPendingInvitee       -> handleSelectUser(action)
+            is UserListAction.RemovePendingInvitee       -> handleRemoveSelectedUser(action)
+            UserListAction.ComputeMatrixToLinkForSharing -> handleShareMyMatrixToLink()
+        }.exhaustive
+    }
+
+    private fun handleSearchUsers(searchTerm: String) {
+        setState {
+            copy(searchTerm = searchTerm)
+        }
+        knownUsersSearch.accept(searchTerm)
+        directoryUsersSearch.accept(searchTerm)
+    }
+
+    private fun handleShareMyMatrixToLink() {
+        session.permalinkService().createPermalink(session.myUserId)?.let {
+            _viewEvents.post(UserListViewEvents.OpenShareMatrixToLing(it))
+        }
+    }
+
+    private fun handleClearSearchUsers() {
+        knownUsersSearch.accept("")
+        directoryUsersSearch.accept("")
+        setState {
+            copy(searchTerm = "")
+        }
+    }
+
+    private fun observeUsers() = withState { state ->
+        knownUsersSearch
+                .throttleLast(300, TimeUnit.MILLISECONDS)
+                .observeOn(AndroidSchedulers.mainThread())
+                .switchMap {
+                    session.rx().livePagedUsers(it, state.excludedUserIds)
+                }
+                .execute { async ->
+                    copy(knownUsers = async)
+                }
+
+        currentUserSearchDisposable?.dispose()
+        directoryUsersSearch
+                .debounce(300, TimeUnit.MILLISECONDS)
+                .switchMapSingle { search ->
+                    val stream = if (search.isBlank()) {
+                        Single.just(emptyList())
+                    } else if (MatrixPatterns.isUserId(search)) {
+                        // If it's a valid user id try to use Profile API
+                        // because directory only returns users that are in public rooms or share a room with you, where as
+                        // profile will work other federations
+                        session.rx().searchUsersDirectory(search, 50, state.excludedUserIds ?: emptySet())
+                                .map { users ->
+                                    users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
+                                }
+                                .zipWith(
+                                        session.rx().getProfileInfo(search)
+                                                // ... not sure how to handle that properly (manage error case in map and return optional)
+                                                .onErrorReturn { USER_NOT_FOUND_MAP }
+                                                .map { json ->
+                                                    if (json === USER_NOT_FOUND_MAP) {
+                                                        USER_NOT_FOUND
+                                                    } else {
+                                                        User(
+                                                                userId = search,
+                                                                displayName = json[ProfileService.DISPLAY_NAME_KEY] as? String,
+                                                                avatarUrl = json[ProfileService.AVATAR_URL_KEY] as? String
+                                                        )
+                                                    }
+                                                },
+                                        { t1, t2 ->
+                                            if (t2 == USER_NOT_FOUND) {
+                                                t1
+                                            }
+                                            // profile result might also be in search results, in this case keep search result
+                                            else if (t1.indexOfFirst { it.userId == t2.userId } != -1) {
+                                                t1
+                                            } else {
+                                                // put it first
+                                                listOf(t2) + t1
+                                            }
+                                        }
+                                )
+                                .doOnSubscribe {
+                                    currentUserSearchDisposable = it
+                                }
+                                .doOnDispose {
+                                    currentUserSearchDisposable = null
+                                }
+                    } else {
+                        session.rx()
+                                .searchUsersDirectory(search, 50, state.excludedUserIds ?: emptySet())
+                                .map { users ->
+                                    users.sortedBy { it.toMatrixItem().firstLetterOfDisplayName() }
+                                }
+                                .doOnSubscribe {
+                                    currentUserSearchDisposable = it
+                                }
+                                .doOnDispose {
+                                    currentUserSearchDisposable = null
+                                }
+                    }
+                    stream.toAsync {
+                        copy(directoryUsers = it)
+                    }
+                }
+                .subscribe()
+                .disposeOnClear()
+    }
+
+    private fun handleSelectUser(action: UserListAction.SelectPendingInvitee) = withState { state ->
+        val selectedUsers = state.pendingInvitees.toggle(action.pendingInvitee)
+        setState { copy(pendingInvitees = selectedUsers) }
+    }
+
+    private fun handleRemoveSelectedUser(action: UserListAction.RemovePendingInvitee) = withState { state ->
+        val selectedUsers = state.pendingInvitees.minus(action.pendingInvitee)
+        setState { copy(pendingInvitees = selectedUsers) }
+    }
+}
diff --git a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewState.kt b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt
similarity index 78%
rename from vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewState.kt
rename to vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt
index fe79a8ab37..f7cf421ca8 100644
--- a/vector/src/main/java/im/vector/app/features/userdirectory/UserDirectoryViewState.kt
+++ b/vector/src/main/java/im/vector/app/features/userdirectory/UserListViewState.kt
@@ -17,30 +17,29 @@
 package im.vector.app.features.userdirectory
 
 import androidx.paging.PagedList
-import arrow.core.Option
 import com.airbnb.mvrx.Async
 import com.airbnb.mvrx.MvRxState
 import com.airbnb.mvrx.Uninitialized
+import im.vector.app.core.contacts.MappedContact
 import org.matrix.android.sdk.api.session.user.model.User
 
-data class UserDirectoryViewState(
+data class UserListViewState(
         val excludedUserIds: Set<String>? = null,
         val knownUsers: Async<PagedList<User>> = Uninitialized,
         val directoryUsers: Async<List<User>> = Uninitialized,
+        val filteredMappedContacts: List<MappedContact> = emptyList(),
         val pendingInvitees: Set<PendingInvitee> = emptySet(),
         val createAndInviteState: Async<String> = Uninitialized,
-        val directorySearchTerm: String = "",
-        val filterKnownUsersValue: Option<String> = Option.empty(),
-        val existingDmRoomId: String? = null
+        val searchTerm: String = "",
+        val myUserId: String = "",
+        val existingRoomId: String? = null
 ) : MvRxState {
 
-    constructor(args: KnownUsersFragmentArgs) : this(excludedUserIds = args.excludedUserIds)
-
     fun getSelectedMatrixId(): List<String> {
         return pendingInvitees
                 .mapNotNull {
                     when (it) {
-                        is PendingInvitee.UserPendingInvitee     -> it.user.userId
+                        is PendingInvitee.UserPendingInvitee -> it.user.userId
                         is PendingInvitee.ThreePidPendingInvitee -> null
                     }
                 }
diff --git a/vector/src/main/res/drawable/ic_book.xml b/vector/src/main/res/drawable/ic_book.xml
new file mode 100644
index 0000000000..3cd7357248
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_book.xml
@@ -0,0 +1,21 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M4,19.5C4,18.1193 5.1193,17 6.5,17H20"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M6.5,2H20V22H6.5C5.1193,22 4,20.8807 4,19.5V4.5C4,3.1193 5.1193,2 6.5,2Z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#ffffff"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/vector/src/main/res/drawable/ic_invite_people.xml b/vector/src/main/res/drawable/ic_invite_people.xml
new file mode 100644
index 0000000000..3ec60095ff
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_invite_people.xml
@@ -0,0 +1,10 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M19.1001,9C18.7779,9 18.5168,8.7388 18.5168,8.4167V6.0833H16.1834C15.8613,6.0833 15.6001,5.8222 15.6001,5.5C15.6001,5.1778 15.8613,4.9167 16.1834,4.9167H18.5168V2.5833C18.5168,2.2612 18.7779,2 19.1001,2C19.4223,2 19.6834,2.2612 19.6834,2.5833V4.9167H22.0168C22.3389,4.9167 22.6001,5.1778 22.6001,5.5C22.6001,5.8222 22.3389,6.0833 22.0168,6.0833H19.6834V8.4167C19.6834,8.7388 19.4223,9 19.1001,9ZM19.6001,11C20.0669,11 20.5212,10.9467 20.9574,10.8458C21.1161,11.5383 21.2,12.2594 21.2,13C21.2,16.1409 19.6917,18.9294 17.3598,20.6808V20.6807C16.0014,21.7011 14.3635,22.3695 12.5815,22.5505C12.2588,22.5832 11.9314,22.6 11.6,22.6C6.2981,22.6 2,18.302 2,13C2,7.6981 6.2981,3.4 11.6,3.4C12.3407,3.4 13.0618,3.4839 13.7543,3.6427C13.6534,4.0788 13.6001,4.5332 13.6001,5C13.6001,8.3137 16.2864,11 19.6001,11ZM11.5999,20.68C13.6754,20.68 15.5585,19.8567 16.9407,18.5189C16.0859,16.4086 14.0167,14.92 11.5998,14.92C9.183,14.92 7.1138,16.4086 6.259,18.5189C7.6411,19.8567 9.5244,20.68 11.5999,20.68ZM11.7426,7.4117C10.3168,7.5417 9.2,8.7404 9.2,10.2C9.2,11.7464 10.4536,13 12,13C13.0308,13 13.9315,12.443 14.4176,11.6135C13.0673,10.6058 12.0929,9.1225 11.7426,7.4117Z"
+      android:fillColor="#ffffff"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/vector/src/main/res/drawable/ic_picture_icon.xml b/vector/src/main/res/drawable/ic_picture_icon.xml
new file mode 100644
index 0000000000..c978a714ab
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_picture_icon.xml
@@ -0,0 +1,29 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M3,5C3,3.8954 3.8954,3 5,3H19C20.1046,3 21,3.8954 21,5V19C21,20.1046 20.1046,21 19,21H5C3.8954,21 3,20.1046 3,19V5Z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#ffffff"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M8.5,10C9.3284,10 10,9.3284 10,8.5C10,7.6716 9.3284,7 8.5,7C7.6716,7 7,7.6716 7,8.5C7,9.3284 7.6716,10 8.5,10Z"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#ffffff"
+      android:strokeLineCap="round"/>
+  <path
+      android:pathData="M21,15L16,10L5,21"
+      android:strokeLineJoin="round"
+      android:strokeWidth="2"
+      android:fillColor="#00000000"
+      android:strokeColor="#ffffff"
+      android:strokeLineCap="round"/>
+</vector>
diff --git a/vector/src/main/res/drawable/ic_qr_code_add.xml b/vector/src/main/res/drawable/ic_qr_code_add.xml
new file mode 100644
index 0000000000..32e41f6e57
--- /dev/null
+++ b/vector/src/main/res/drawable/ic_qr_code_add.xml
@@ -0,0 +1,72 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="30dp"
+    android:height="30dp"
+    android:viewportWidth="30"
+    android:viewportHeight="30">
+  <path
+      android:pathData="M29.0625,8.4375C28.5447,8.4375 28.125,8.0177 28.125,7.5L28.125,2.6953C28.125,2.243 27.757,1.875 27.3047,1.875L22.5,1.875C21.9822,1.875 21.5625,1.4552 21.5625,0.9375C21.5625,0.4198 21.9822,0 22.5,0L27.3047,0C28.7909,0 30,1.2091 30,2.6953L30,7.5C30,8.0177 29.5803,8.4375 29.0625,8.4375Z"
+      android:strokeWidth="1"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M0.9375,8.4375C0.4197,8.4375 0,8.0177 0,7.5L0,2.6953C0,1.2091 1.2091,0 2.6953,0L7.5,0C8.0178,0 8.4375,0.4198 8.4375,0.9375C8.4375,1.4552 8.0178,1.875 7.5,1.875L2.6953,1.875C2.243,1.875 1.875,2.243 1.875,2.6953L1.875,7.5C1.875,8.0177 1.4553,8.4375 0.9375,8.4375Z"
+      android:strokeWidth="1"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M7.5,30L2.6953,30C1.2091,30 0,28.7909 0,27.3047L0,22.5C0,21.9823 0.4197,21.5625 0.9375,21.5625C1.4553,21.5625 1.875,21.9823 1.875,22.5L1.875,27.3047C1.875,27.757 2.243,28.125 2.6953,28.125L7.5,28.125C8.0178,28.125 8.4375,28.5448 8.4375,29.0625C8.4375,29.5802 8.0178,30 7.5,30Z"
+      android:strokeWidth="1"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M27.3047,30L22.5,30C21.9822,30 21.5625,29.5802 21.5625,29.0625C21.5625,28.5448 21.9822,28.125 22.5,28.125L27.3047,28.125C27.757,28.125 28.125,27.757 28.125,27.3047L28.125,22.5C28.125,21.9823 28.5447,21.5625 29.0625,21.5625C29.5803,21.5625 30,21.9823 30,22.5L30,27.3047C30,28.7909 28.7909,30 27.3047,30Z"
+      android:strokeWidth="1"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M11.3672,14.0625L6.4453,14.0625C4.9591,14.0625 3.75,12.8534 3.75,11.3672L3.75,6.4453C3.75,4.9591 4.9591,3.75 6.4453,3.75L11.3672,3.75C12.8534,3.75 14.0625,4.9591 14.0625,6.4453L14.0625,11.3672C14.0625,12.8534 12.8534,14.0625 11.3672,14.0625ZM6.4453,5.625C5.993,5.625 5.625,5.993 5.625,6.4453L5.625,11.3672C5.625,11.8195 5.993,12.1875 6.4453,12.1875L11.3672,12.1875C11.8195,12.1875 12.1875,11.8195 12.1875,11.3672L12.1875,6.4453C12.1875,5.993 11.8195,5.625 11.3672,5.625L6.4453,5.625Z"
+      android:strokeWidth="1"
+      android:fillColor="#000000"
+      android:fillType="nonZero"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M11.3672,26.25L6.4453,26.25C4.9591,26.25 3.75,25.0409 3.75,23.5547L3.75,18.6328C3.75,17.1466 4.9591,15.9375 6.4453,15.9375L11.3672,15.9375C12.8534,15.9375 14.0625,17.1466 14.0625,18.6328L14.0625,23.5547C14.0625,25.0409 12.8534,26.25 11.3672,26.25ZM6.4453,17.8125C5.993,17.8125 5.625,18.1805 5.625,18.6328L5.625,23.5547C5.625,24.007 5.993,24.375 6.4453,24.375L11.3672,24.375C11.8195,24.375 12.1875,24.007 12.1875,23.5547L12.1875,18.6328C12.1875,18.1805 11.8195,17.8125 11.3672,17.8125L6.4453,17.8125Z"
+      android:strokeWidth="1"
+      android:fillColor="#000000"
+      android:fillType="nonZero"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M23.5547,14.0625L18.6328,14.0625C17.1466,14.0625 15.9375,12.8534 15.9375,11.3672L15.9375,6.4453C15.9375,4.9591 17.1466,3.75 18.6328,3.75L23.5547,3.75C25.0409,3.75 26.25,4.9591 26.25,6.4453L26.25,11.3672C26.25,12.8534 25.0409,14.0625 23.5547,14.0625ZM18.6328,5.625C18.1805,5.625 17.8125,5.993 17.8125,6.4453L17.8125,11.3672C17.8125,11.8195 18.1805,12.1875 18.6328,12.1875L23.5547,12.1875C24.007,12.1875 24.375,11.8195 24.375,11.3672L24.375,6.4453C24.375,5.993 24.007,5.625 23.5547,5.625L18.6328,5.625Z"
+      android:strokeWidth="1"
+      android:fillColor="#000000"
+      android:fillType="nonZero"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M21.918,21.918L21.918,25.2871C21.918,25.7563 21.5376,26.1367 21.0684,26.1367C20.5991,26.1367 20.2188,25.7563 20.2188,25.2871L20.2188,21.918L16.8496,21.918C16.3804,21.918 16,21.5376 16,21.0684C16,20.5991 16.3804,20.2188 16.8496,20.2188L20.2188,20.2188L20.2188,16.8496C20.2188,16.3804 20.5991,16 21.0684,16C21.5376,16 21.918,16.3804 21.918,16.8496L21.918,20.2188L25.2871,20.2188C25.7563,20.2188 26.1367,20.5991 26.1367,21.0684C26.1367,21.5376 25.7563,21.918 25.2871,21.918L21.918,21.918Z"
+      android:strokeWidth="1"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M9.375,10.3125L8.4375,10.3125C7.9197,10.3125 7.5,9.8927 7.5,9.375L7.5,8.4375C7.5,7.9198 7.9197,7.5 8.4375,7.5L9.375,7.5C9.8928,7.5 10.3125,7.9198 10.3125,8.4375L10.3125,9.375C10.3125,9.8927 9.8928,10.3125 9.375,10.3125Z"
+      android:strokeWidth="1"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M21.5625,10.3125L20.625,10.3125C20.1072,10.3125 19.6875,9.8927 19.6875,9.375L19.6875,8.4375C19.6875,7.9198 20.1072,7.5 20.625,7.5L21.5625,7.5C22.0803,7.5 22.5,7.9198 22.5,8.4375L22.5,9.375C22.5,9.8927 22.0803,10.3125 21.5625,10.3125Z"
+      android:strokeWidth="1"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+  <path
+      android:pathData="M9.375,22.5L8.4375,22.5C7.9197,22.5 7.5,22.0802 7.5,21.5625L7.5,20.625C7.5,20.1073 7.9197,19.6875 8.4375,19.6875L9.375,19.6875C9.8928,19.6875 10.3125,20.1073 10.3125,20.625L10.3125,21.5625C10.3125,22.0802 9.8928,22.5 9.375,22.5Z"
+      android:strokeWidth="1"
+      android:fillColor="#000000"
+      android:fillType="evenOdd"
+      android:strokeColor="#00000000"/>
+</vector>
diff --git a/vector/src/main/res/layout/activity_simple.xml b/vector/src/main/res/layout/activity_simple.xml
index 0eda46a67d..c6f2af8171 100644
--- a/vector/src/main/res/layout/activity_simple.xml
+++ b/vector/src/main/res/layout/activity_simple.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout
+<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/vector_coordinator_layout"
-    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
 
@@ -10,4 +10,24 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
 
+    <RelativeLayout
+        android:id="@+id/simpleActivityWaitingView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:background="?attr/colorBackgroundFloating"
+        android:gravity="center"
+        android:padding="8dp"
+        android:visibility="gone"
+        tools:visibility="visible">
+
+        <ProgressBar
+            android:id="@+id/waiting_view_status_circular_progress"
+            android:layout_width="40dp"
+            android:layout_height="40dp"
+            android:layout_centerInParent="true"
+            android:layout_gravity="center_vertical" />
+
+    </RelativeLayout>
+
 </androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_home_drawer.xml b/vector/src/main/res/layout/fragment_home_drawer.xml
index d34ea0b815..2f1d7cc4c4 100644
--- a/vector/src/main/res/layout/fragment_home_drawer.xml
+++ b/vector/src/main/res/layout/fragment_home_drawer.xml
@@ -31,10 +31,11 @@
 
         <ImageView
             android:id="@+id/homeDrawerHeaderAvatarView"
-            android:layout_width="64dp"
-            android:layout_height="64dp"
+            android:layout_width="50dp"
+            android:layout_height="50dp"
             android:layout_marginStart="@dimen/layout_horizontal_margin"
             android:layout_marginTop="24dp"
+            android:transitionName="profile"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent"
             tools:src="@tools:sample/avatars" />
@@ -43,13 +44,13 @@
             android:id="@+id/homeDrawerUsernameView"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_marginTop="24dp"
-            android:layout_marginEnd="@dimen/layout_horizontal_margin"
+            android:layout_marginTop="16dp"
+            android:layout_marginEnd="8dp"
             android:maxLines="1"
             android:singleLine="true"
             android:textColor="?riotx_text_primary"
             android:textSize="15sp"
-            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintEnd_toStartOf="@+id/homeDrawerQRCodeButton"
             app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView"
             app:layout_constraintTop_toBottomOf="@+id/homeDrawerHeaderAvatarView"
             tools:text="@sample/matrix.json/data/displayName" />
@@ -58,18 +59,69 @@
             android:id="@+id/homeDrawerUserIdView"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
-            android:layout_marginEnd="@dimen/layout_horizontal_margin"
-            android:layout_marginBottom="17dp"
+            android:layout_marginEnd="8dp"
             android:maxLines="1"
             android:singleLine="true"
             android:textColor="?riotx_text_secondary"
             android:textSize="15sp"
-            app:layout_constraintBottom_toBottomOf="parent"
-            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintBottom_toTopOf="@+id/homeDrawerInviteFriendButton"
+            app:layout_constraintEnd_toStartOf="@+id/homeDrawerQRCodeButton"
             app:layout_constraintStart_toStartOf="@+id/homeDrawerHeaderAvatarView"
             app:layout_constraintTop_toBottomOf="@+id/homeDrawerUsernameView"
             tools:text="@sample/matrix.json/data/mxid" />
 
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/homeDrawerQRCodeButton"
+            style="@style/Widget.MaterialComponents.Button.Icon"
+            android:layout_width="34dp"
+            android:layout_height="34dp"
+            android:layout_marginTop="2dp"
+            android:layout_marginEnd="@dimen/layout_horizontal_margin"
+            android:backgroundTint="?riotx_bottom_nav_background_color"
+            android:elevation="0dp"
+            android:insetLeft="0dp"
+            android:insetTop="0dp"
+            android:insetRight="0dp"
+            android:insetBottom="0dp"
+            android:padding="0dp"
+            app:cornerRadius="17dp"
+            app:icon="@drawable/ic_qr_code_add"
+            app:iconGravity="textStart"
+            app:iconPadding="0dp"
+            app:iconSize="20dp"
+            app:iconTint="@color/riotx_accent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintTop_toTopOf="@id/homeDrawerUsernameView" />
+
+
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/homeDrawerInviteFriendButton"
+            style="@style/Widget.MaterialComponents.Button.TextButton"
+            android:layout_width="wrap_content"
+            android:layout_height="36dp"
+            android:layout_marginStart="@dimen/layout_horizontal_margin"
+            android:layout_marginTop="8dp"
+            android:layout_marginEnd="@dimen/layout_horizontal_margin"
+            android:layout_marginBottom="8dp"
+            android:gravity="center"
+            android:insetTop="0dp"
+            android:insetBottom="0dp"
+            android:padding="0dp"
+            android:text="@string/invite_friends"
+            android:textAllCaps="false"
+            android:textColor="?colorAccent"
+            android:textSize="13sp"
+            app:icon="@drawable/ic_invite_people"
+            app:iconGravity="textStart"
+            app:iconSize="20dp"
+            app:iconTint="?colorAccent"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintHorizontal_bias="0"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/homeDrawerUserIdView" />
+
+
     </androidx.constraintlayout.widget.ConstraintLayout>
 
     <androidx.fragment.app.FragmentContainerView
diff --git a/vector/src/main/res/layout/fragment_matrix_to_card.xml b/vector/src/main/res/layout/fragment_matrix_to_card.xml
new file mode 100644
index 0000000000..7356f105e0
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_matrix_to_card.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+
+    <ImageView
+        android:id="@+id/matrixToCardAvatar"
+        android:layout_width="60dp"
+        android:layout_height="60dp"
+        android:layout_marginStart="@dimen/layout_horizontal_margin"
+        android:layout_marginTop="@dimen/layout_vertical_margin_big"
+        android:elevation="4dp"
+        android:transitionName="profile"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        tools:src="@tools:sample/avatars" />
+
+    <TextView
+        android:id="@+id/matrixToCardNameText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="@dimen/layout_vertical_margin_big"
+        android:layout_marginEnd="16dp"
+        android:maxLines="1"
+        android:singleLine="true"
+        android:textAlignment="center"
+        android:textColor="?riotx_text_primary"
+        android:textSize="15sp"
+        app:layout_constraintTop_toBottomOf="@+id/matrixToCardAvatar"
+        tools:text="@sample/matrix.json/data/displayName" />
+
+    <TextView
+        android:id="@+id/matrixToCardUserIdText"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginStart="16dp"
+        android:layout_marginTop="4dp"
+        android:layout_marginEnd="16dp"
+        android:maxLines="1"
+        android:singleLine="true"
+        android:textAlignment="center"
+        android:textColor="?riotx_text_secondary"
+        android:textSize="15sp"
+        app:layout_constraintTop_toBottomOf="@id/matrixToCardNameText"
+        tools:text="@sample/matrix.json/data/mxid" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/matrixToCardSendMessageButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:layout_marginBottom="@dimen/layout_vertical_margin_big"
+        android:minWidth="130dp"
+        android:text="@string/start_chatting"
+        app:icon="@drawable/ic_fab_add_chat"
+        app:iconTint="@color/white"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/matrixToCardUserIdText"
+        app:layout_constraintVertical_bias="0" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/vector/src/main/res/layout/fragment_qr_code_scanner.xml b/vector/src/main/res/layout/fragment_qr_code_scanner.xml
index 589b7c73d4..135a856f4a 100644
--- a/vector/src/main/res/layout/fragment_qr_code_scanner.xml
+++ b/vector/src/main/res/layout/fragment_qr_code_scanner.xml
@@ -15,4 +15,28 @@
 
     <!-- TODO In the future we could add a toggle to switch the flash, and other possible settings -->
 
+    <!-- TODO add take from album option.. -->
+<!--    <com.google.android.material.button.MaterialButton-->
+<!--        android:id="@+id/openAlbumButton"-->
+<!--        style="@style/Widget.MaterialComponents.Button.Icon"-->
+<!--        android:layout_width="34dp"-->
+<!--        android:layout_height="34dp"-->
+<!--        android:layout_marginEnd="@dimen/layout_horizontal_margin"-->
+<!--        android:layout_marginBottom="@dimen/layout_vertical_margin_big"-->
+<!--        android:backgroundTint="?riotx_bottom_nav_background_color"-->
+<!--        android:elevation="0dp"-->
+<!--        android:insetLeft="0dp"-->
+<!--        android:insetTop="0dp"-->
+<!--        android:insetRight="0dp"-->
+<!--        android:insetBottom="0dp"-->
+<!--        android:padding="0dp"-->
+<!--        app:cornerRadius="17dp"-->
+<!--        app:icon="@drawable/ic_picture_icon"-->
+<!--        app:iconGravity="textStart"-->
+<!--        app:iconPadding="0dp"-->
+<!--        app:iconSize="20dp"-->
+<!--        app:iconTint="@color/riotx_accent"-->
+<!--        app:layout_constraintBottom_toBottomOf="parent"-->
+<!--        app:layout_constraintEnd_toEndOf="parent"/>-->
+
 </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_qr_code_scanner_with_button.xml b/vector/src/main/res/layout/fragment_qr_code_scanner_with_button.xml
new file mode 100644
index 0000000000..6a59138990
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_qr_code_scanner_with_button.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <me.dm7.barcodescanner.zxing.ZXingScannerView
+        android:id="@+id/userCodeScannerView"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/userCodeMyCodeButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginBottom="@dimen/layout_vertical_margin_big"
+        android:maxWidth="160dp"
+        android:text="@string/user_code_my_code"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent" />
+
+    <com.google.android.material.button.MaterialButton
+        android:id="@+id/userCodeOpenGalleryButton"
+        style="@style/Widget.MaterialComponents.Button.Icon"
+        android:layout_width="34dp"
+        android:layout_height="34dp"
+        android:layout_marginTop="2dp"
+        android:layout_marginEnd="@dimen/layout_horizontal_margin"
+        android:backgroundTint="?riotx_bottom_nav_background_color"
+        android:elevation="0dp"
+        android:insetLeft="0dp"
+        android:insetTop="0dp"
+        android:insetRight="0dp"
+        android:insetBottom="0dp"
+        android:padding="0dp"
+        app:cornerRadius="17dp"
+        app:icon="@drawable/ic_picture_icon"
+        app:iconGravity="textStart"
+        app:iconPadding="0dp"
+        app:iconSize="20dp"
+        app:iconTint="@color/riotx_accent"
+        app:layout_constraintBottom_toBottomOf="@id/userCodeMyCodeButton"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintEnd_toStartOf="@id/userCodeMyCodeButton"
+        app:layout_constraintTop_toTopOf="@id/userCodeMyCodeButton" />
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_room_list.xml b/vector/src/main/res/layout/fragment_room_list.xml
index 855c45f7c5..72266cc21a 100644
--- a/vector/src/main/res/layout/fragment_room_list.xml
+++ b/vector/src/main/res/layout/fragment_room_list.xml
@@ -23,16 +23,20 @@
         tools:showPaths="true"
         tools:visibility="visible" />
 
-    <im.vector.app.features.home.room.list.widget.DmsFabMenuView
-        android:id="@+id/createDmFabMenu"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
+    <com.google.android.material.floatingactionbutton.FloatingActionButton
+        android:id="@+id/createChatRoomButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="bottom|end"
+        android:layout_marginEnd="16dp"
+        android:layout_marginBottom="16dp"
+        android:accessibilityTraversalBefore="@+id/roomListView"
         android:contentDescription="@string/a11y_create_direct_message"
+        android:scaleType="center"
         android:src="@drawable/ic_fab_add_chat"
         android:visibility="gone"
         app:maxImageSize="34dp"
-        app:layoutDescription="@xml/motion_scene_dms_fab_menu"
-        tools:showPaths="true"
+        tools:layout_marginEnd="80dp"
         tools:visibility="visible" />
 
     <com.google.android.material.floatingactionbutton.FloatingActionButton
diff --git a/vector/src/main/res/layout/fragment_user_code_show.xml b/vector/src/main/res/layout/fragment_user_code_show.xml
new file mode 100644
index 0000000000..7d96b80dbe
--- /dev/null
+++ b/vector/src/main/res/layout/fragment_user_code_show.xml
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<ScrollView 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
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
+
+        <androidx.appcompat.widget.Toolbar
+            android:id="@+id/showUserCodeToolBar"
+            style="@style/VectorToolbarStyle"
+            android:layout_width="0dp"
+            android:layout_height="?actionBarSize"
+            android:elevation="4dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent">
+
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent">
+
+                <ImageView
+                    android:id="@+id/showUserCodeClose"
+                    android:layout_width="@dimen/layout_touch_size"
+                    android:layout_height="@dimen/layout_touch_size"
+                    android:scaleType="center"
+                    android:src="@drawable/ic_x_18dp"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+                <TextView
+                    android:id="@+id/showUserCodeTitle"
+                    android:layout_width="0dp"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="8dp"
+                    android:layout_marginEnd="8dp"
+                    android:ellipsize="end"
+                    android:maxLines="1"
+                    android:text="@string/add_by_qr_code"
+                    android:textColor="?riotx_text_primary"
+                    android:textSize="18sp"
+                    android:textStyle="bold"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintHorizontal_bias="0.0"
+                    app:layout_constraintStart_toEndOf="@+id/showUserCodeClose"
+                    app:layout_constraintTop_toTopOf="parent" />
+
+            </androidx.constraintlayout.widget.ConstraintLayout>
+
+        </androidx.appcompat.widget.Toolbar>
+
+
+        <ImageView
+            android:id="@+id/showUserCodeAvatar"
+            android:transitionName="profile"
+            android:layout_width="60dp"
+            android:layout_height="60dp"
+            android:layout_marginStart="@dimen/layout_horizontal_margin"
+            android:elevation="4dp"
+            app:layout_constraintBottom_toBottomOf="@id/showUserCodeCardTopBarrier"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="@id/showUserCodeCardTopBarrier"
+            tools:src="@tools:sample/avatars" />
+
+        <androidx.constraintlayout.widget.Barrier
+            android:id="@+id/showUserCodeCardTopBarrier"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            app:barrierDirection="top"
+            app:constraint_referenced_ids="showUserCodeCard" />
+
+        <androidx.cardview.widget.CardView
+            android:id="@+id/showUserCodeCard"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:layout_marginTop="50dp"
+            android:padding="16dp"
+            app:cardCornerRadius="20dp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@+id/showUserCodeToolBar"
+            app:layout_constraintWidth_percent="0.8">
+
+            <androidx.constraintlayout.widget.ConstraintLayout
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:minWidth="300dp">
+
+                <TextView
+                    android:id="@+id/showUserCodeCardNameText"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="16dp"
+                    android:layout_marginTop="40dp"
+                    android:layout_marginEnd="16dp"
+                    android:maxLines="1"
+                    android:singleLine="true"
+                    android:textAlignment="center"
+                    android:textColor="?riotx_text_primary"
+                    android:textSize="15sp"
+                    app:layout_constraintTop_toTopOf="parent"
+                    tools:text="@sample/matrix.json/data/displayName" />
+
+                <TextView
+                    android:id="@+id/showUserCodeCardUserIdText"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginStart="16dp"
+                    android:layout_marginTop="4dp"
+                    android:layout_marginEnd="16dp"
+                    android:maxLines="1"
+                    android:singleLine="true"
+                    android:textAlignment="center"
+                    android:textColor="?riotx_text_secondary"
+                    android:textSize="15sp"
+                    app:layout_constraintTop_toBottomOf="@id/showUserCodeCardNameText"
+                    tools:text="@sample/matrix.json/data/mxid" />
+
+
+                <!--                android:id="@+id/itemShareQrCodeImage"-->
+                <!--                android:layout_width="300dp"-->
+                <!--                android:layout_height="300dp"-->
+                <!--                android:layout_gravity="center_horizontal"-->
+                <!--                android:contentDescription="@string/a11y_qr_code_for_verification"-->
+                <!--                tools:src="@color/riotx_header_panel_background_black" />-->
+
+                <im.vector.app.core.ui.views.QrCodeImageView
+                    android:id="@+id/showUserCodeQRImage"
+                    android:layout_width="260dp"
+                    android:layout_height="260dp"
+                    android:layout_gravity="center"
+                    android:layout_marginTop="10dp"
+                    android:layout_marginBottom="@dimen/layout_vertical_margin"
+                    app:layout_constraintBottom_toBottomOf="parent"
+                    app:layout_constraintEnd_toEndOf="parent"
+                    app:layout_constraintStart_toStartOf="parent"
+                    app:layout_constraintTop_toBottomOf="@id/showUserCodeCardUserIdText"
+                    tools:src="@drawable/ic_qr_code_add" />
+
+            </androidx.constraintlayout.widget.ConstraintLayout>
+
+        </androidx.cardview.widget.CardView>
+
+
+        <TextView
+            android:id="@+id/showUserCodeInfoText"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:layout_marginTop="16dp"
+            android:layout_marginEnd="16dp"
+            android:text="@string/user_code_info_text"
+            android:textAlignment="center"
+            android:textColor="?riotx_text_secondary"
+            android:textSize="15sp"
+            app:layout_constraintEnd_toEndOf="@id/showUserCodeCard"
+            app:layout_constraintStart_toStartOf="@id/showUserCodeCard"
+            app:layout_constraintTop_toBottomOf="@id/showUserCodeCard" />
+
+        <com.google.android.material.button.MaterialButton
+            android:id="@+id/showUserCodeScanButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:minWidth="130dp"
+            android:text="@string/user_code_scan"
+            app:layout_constraintBottom_toBottomOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toBottomOf="@id/showUserCodeInfoText"
+            app:layout_constraintVertical_bias="0" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</ScrollView>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/fragment_known_users.xml b/vector/src/main/res/layout/fragment_user_list.xml
similarity index 68%
rename from vector/src/main/res/layout/fragment_known_users.xml
rename to vector/src/main/res/layout/fragment_user_list.xml
index cf2d4e8025..15884502ad 100644
--- a/vector/src/main/res/layout/fragment_known_users.xml
+++ b/vector/src/main/res/layout/fragment_user_list.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
@@ -10,7 +10,7 @@
         android:layout_height="match_parent">
 
         <androidx.appcompat.widget.Toolbar
-            android:id="@+id/knownUsersToolbar"
+            android:id="@+id/userListToolbar"
             style="@style/VectorToolbarStyle"
             android:layout_width="0dp"
             android:layout_height="?actionBarSize"
@@ -24,7 +24,7 @@
                 android:layout_height="match_parent">
 
                 <ImageView
-                    android:id="@+id/knownUsersClose"
+                    android:id="@+id/userListClose"
                     android:layout_width="@dimen/layout_touch_size"
                     android:layout_height="@dimen/layout_touch_size"
                     android:clickable="true"
@@ -37,7 +37,7 @@
                     app:layout_constraintTop_toTopOf="parent" />
 
                 <TextView
-                    android:id="@+id/knownUsersTitle"
+                    android:id="@+id/userListTitle"
                     android:layout_width="0dp"
                     android:layout_height="wrap_content"
                     android:layout_marginStart="8dp"
@@ -51,7 +51,7 @@
                     app:layout_constraintBottom_toBottomOf="parent"
                     app:layout_constraintEnd_toEndOf="parent"
                     app:layout_constraintHorizontal_bias="0.0"
-                    app:layout_constraintStart_toEndOf="@+id/knownUsersClose"
+                    app:layout_constraintStart_toEndOf="@+id/userListClose"
                     app:layout_constraintTop_toTopOf="parent" />
 
             </androidx.constraintlayout.widget.ConstraintLayout>
@@ -67,7 +67,7 @@
             android:layout_marginEnd="@dimen/layout_horizontal_margin"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/knownUsersToolbar"
+            app:layout_constraintTop_toBottomOf="@+id/userListToolbar"
             app:maxHeight="64dp">
 
             <com.google.android.material.chip.ChipGroup
@@ -79,7 +79,7 @@
         </im.vector.app.core.platform.MaxHeightScrollView>
 
         <EditText
-            android:id="@+id/knownUsersFilter"
+            android:id="@+id/userListSearch"
             android:layout_width="0dp"
             android:layout_height="wrap_content"
             android:layout_marginStart="@dimen/layout_horizontal_margin"
@@ -87,7 +87,7 @@
             android:background="@null"
             android:drawablePadding="8dp"
             android:gravity="center_vertical"
-            android:hint="@string/direct_room_filter_hint"
+            android:hint="@string/user_directory_search_hint"
             android:importantForAutofill="no"
             android:inputType="text"
             android:maxHeight="80dp"
@@ -98,17 +98,17 @@
             app:layout_constraintTop_toBottomOf="@+id/chipGroupScrollView" />
 
         <View
-            android:id="@+id/knownUsersFilterDivider"
+            android:id="@+id/userListFilterDivider"
             android:layout_width="0dp"
             android:layout_height="1dp"
             android:background="?attr/vctr_list_divider_color"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/knownUsersFilter" />
+            app:layout_constraintTop_toBottomOf="@+id/userListSearch" />
 
 
         <TextView
-            android:id="@+id/knownUsersE2EbyDefaultDisabled"
+            android:id="@+id/userListE2EbyDefaultDisabled"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_margin="16dp"
@@ -117,44 +117,9 @@
             android:textColor="?riotx_text_secondary"
             android:textSize="14sp"
             android:visibility="gone"
-            app:layout_constraintTop_toBottomOf="@id/knownUsersFilterDivider"
+            app:layout_constraintTop_toBottomOf="@id/userListFilterDivider"
             tools:visibility="visible" />
 
-
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/addByMatrixId"
-            style="@style/VectorButtonStyleText"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginTop="8dp"
-            android:layout_marginBottom="8dp"
-            android:minHeight="@dimen/layout_touch_size"
-            android:text="@string/add_by_matrix_id"
-            android:visibility="visible"
-            app:icon="@drawable/ic_plus_circle"
-            app:iconPadding="13dp"
-            app:iconTint="@color/riotx_accent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/knownUsersE2EbyDefaultDisabled" />
-
-        <com.google.android.material.button.MaterialButton
-            android:id="@+id/addFromPhoneBook"
-            style="@style/VectorButtonStyleText"
-            android:layout_width="0dp"
-            android:layout_height="wrap_content"
-            android:layout_marginStart="16dp"
-            android:layout_marginTop="8dp"
-            android:layout_marginBottom="8dp"
-            android:minHeight="@dimen/layout_touch_size"
-            android:text="@string/search_in_my_contacts"
-            android:visibility="visible"
-            app:icon="@drawable/ic_plus_circle"
-            app:iconPadding="13dp"
-            app:iconTint="@color/riotx_accent"
-            app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@id/addByMatrixId" />
-
         <androidx.recyclerview.widget.RecyclerView
             android:id="@+id/knownUsersRecyclerView"
             android:layout_width="0dp"
@@ -166,10 +131,8 @@
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
-            app:layout_constraintTop_toBottomOf="@+id/addFromPhoneBook"
+            app:layout_constraintTop_toBottomOf="@+id/userListE2EbyDefaultDisabled"
             tools:listitem="@layout/item_known_user" />
 
     </androidx.constraintlayout.widget.ConstraintLayout>
-
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
-
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_checkbox.xml b/vector/src/main/res/layout/item_checkbox.xml
new file mode 100644
index 0000000000..c7427b46c8
--- /dev/null
+++ b/vector/src/main/res/layout/item_checkbox.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.checkbox.MaterialCheckBox xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/checkbox"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginStart="@dimen/layout_horizontal_margin"
+    android:layout_marginTop="4dp"
+    android:layout_marginEnd="@dimen/layout_horizontal_margin"
+    android:text="@string/matrix_only_filter"
+    app:layout_constraintEnd_toEndOf="parent"
+    app:layout_constraintStart_toStartOf="parent"
+    app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer" />
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_contact_action.xml b/vector/src/main/res/layout/item_contact_action.xml
new file mode 100644
index 0000000000..daea0d5154
--- /dev/null
+++ b/vector/src/main/res/layout/item_contact_action.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="?attr/selectableItemBackground"
+    android:orientation="horizontal"
+    android:paddingStart="8dp"
+    android:paddingEnd="8dp">
+
+    <ImageView
+        android:id="@+id/actionIconImageView"
+        android:layout_width="44dp"
+        android:layout_height="44dp"
+        android:padding="12dp"
+        android:tint="?riotx_text_secondary"
+        tools:src="@drawable/ic_invite_people" />
+
+    <TextView
+        android:id="@+id/actionTitleText"
+        android:layout_width="0dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:layout_weight="1"
+        android:ellipsize="end"
+        android:maxLines="1"
+        android:textColor="?riotx_text_secondary"
+        android:textSize="15sp"
+        tools:text="@string/invite_friends" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/vector/src/main/res/layout/item_user_list_header.xml b/vector/src/main/res/layout/item_user_list_header.xml
new file mode 100644
index 0000000000..26591b68ca
--- /dev/null
+++ b/vector/src/main/res/layout/item_user_list_header.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/userListHeaderView"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_marginStart="8dp"
+    android:fontFamily="sans-serif-medium"
+    android:padding="8dp"
+    android:textColor="?attr/riotx_text_primary"
+    android:textSize="20sp"
+    android:textStyle="normal"
+    tools:text="Recents | Contacts" />
\ No newline at end of file
diff --git a/vector/src/main/res/layout/motion_dms_fab_menu_merge.xml b/vector/src/main/res/layout/motion_dms_fab_menu_merge.xml
deleted file mode 100644
index c0bcec6cb3..0000000000
--- a/vector/src/main/res/layout/motion_dms_fab_menu_merge.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-<?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="match_parent"
-    app:layoutDescription="@xml/motion_scene_dms_fab_menu"
-    tools:motionProgress="0.65"
-    tools:parentTag="androidx.constraintlayout.motion.widget.MotionLayout"
-    tools:showPaths="true">
-
-    <View
-        android:id="@+id/dmsCreateRoomTouchGuard"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:background="?riotx_touch_guard_bg"
-        android:clickable="true"
-        android:contentDescription="@string/a11y_create_menu_close"
-        android:focusable="true" />
-
-    <!-- Sub menu item 2 -->
-    <com.google.android.material.floatingactionbutton.FloatingActionButton
-        android:id="@+id/createDmByQrCode"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom|end"
-        android:accessibilityTraversalBefore="@+id/roomListView"
-        android:contentDescription="@string/a11y_create_direct_message_by_qr_code"
-        android:src="@drawable/ic_fab_add_by_qr_code"
-        app:backgroundTint="#FFFFFF"
-        app:fabCustomSize="48dp"
-        app:maxImageSize="26dp"
-        app:tint="@color/black" />
-
-    <TextView
-        android:id="@+id/createDmByQrCodeLabel"
-        style="@style/VectorLabel"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="8dp"
-        android:ellipsize="end"
-        android:importantForAccessibility="no"
-        android:text="@string/add_by_qr_code" />
-
-    <!-- Sub menu item 1 -->
-    <com.google.android.material.floatingactionbutton.FloatingActionButton
-        android:id="@+id/createDmByMxid"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom|end"
-        android:accessibilityTraversalBefore="@+id/createDmByQrCode"
-        android:contentDescription="@string/a11y_create_direct_message_by_mxid"
-        android:src="@drawable/ic_fab_add_by_mxid"
-        app:backgroundTint="#FFFFFF"
-        app:fabCustomSize="48dp"
-        app:maxImageSize="29dp"
-        app:tint="@color/black" />
-
-    <TextView
-        android:id="@+id/createDmByMxidLabel"
-        style="@style/VectorLabel"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginEnd="8dp"
-        android:ellipsize="end"
-        android:importantForAccessibility="no"
-        android:text="@string/add_by_matrix_id" />
-
-    <!-- Menu -->
-    <com.google.android.material.floatingactionbutton.FloatingActionButton
-        android:id="@+id/dmsCreateRoomButton"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:accessibilityTraversalBefore="@+id/createDmByMxid"
-        android:contentDescription="@string/a11y_create_menu_open"
-        android:src="@drawable/ic_fab_add"
-        app:maxImageSize="14dp" />
-
-</merge>
diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml
index 1eb602e4c3..b6f2e15ab2 100644
--- a/vector/src/main/res/values/strings.xml
+++ b/vector/src/main/res/values/strings.xml
@@ -79,6 +79,7 @@
     <string name="pause_video">Pause</string>
     <string name="dismiss">Dismiss</string>
     <string name="reset">Reset</string>
+    <string name="start_chatting">Start Chatting</string>
 
 
     <!-- First param will be replace by the value of ongoing_conference_call_voice, and second one by the value of ongoing_conference_call_video -->
@@ -1754,6 +1755,7 @@
     <string name="room_filtering_footer_open_room_directory">View the room directory</string>
 
     <string name="room_directory_search_hint">Name or ID (#example:matrix.org)</string>
+    <string name="user_directory_search_hint">Name or ID (like %s)</string>
 
     <string name="labs_swipe_to_reply_in_timeline">Enable swipe to reply in timeline</string>
     <string name="labs_show_unread_notifications_as_tab">Add a dedicated tab for unread notifications on main screen.</string>
@@ -1762,10 +1764,15 @@
 
     <string name="add_by_matrix_id">Add by matrix ID</string>
     <string name="add_by_qr_code">Add by QR code</string>
+    <string name="qr_code">QR code</string>
     <string name="creating_direct_room">"Creating room…"</string>
     <string name="direct_room_no_known_users">"No result found, use Add by matrix ID to search on server."</string>
     <string name="direct_room_start_search">"Start typing to get results"</string>
     <string name="direct_room_filter_hint">"Filter by username or ID…"</string>
+    <string name="direct_room_user_list_recent_title">Recent</string>
+    <string name="direct_room_user_list_known_title">Known Users</string>
+    <string name="direct_room_user_list_contacts_title">Contacts</string>
+    <string name="direct_room_user_list_suggestions_title">Suggestions</string>
 
     <string name="joining_room">"Joining room…"</string>
 
@@ -2540,14 +2547,22 @@
     <string name="invite_users_to_room_action_invite">INVITE</string>
     <string name="inviting_users_to_room">Inviting users…</string>
     <string name="invite_users_to_room_title">Invite Users</string>
+    <string name="invite_friends">Invite Friends</string>
+    <string name="invite_friends_text">Hey, Talk to me on Element: %s</string>
+    <string name="invite_friends_rich_title">🔐️ Join me on element</string>
     <string name="invitation_sent_to_one_user">Invitation sent to %1$s</string>
     <string name="invitations_sent_to_two_users">Invitations sent to %1$s and %2$s</string>
+    <string name="not_a_valid_qr_code">"It's not a valid matrix QR code"</string>
     <plurals name="invitations_sent_to_one_and_more_users">
         <item quantity="one">Invitations sent to %1$s and one more</item>
         <item quantity="other">Invitations sent to %1$s and %2$d more</item>
     </plurals>
     <string name="invite_users_to_room_failure">We could not invite users. Please check the users you want to invite and try again.</string>
 
+    <string name="user_code_scan">Scan</string>
+    <string name="user_code_my_code">My code</string>
+    <string name="user_code_info_text">This is your matrix.to code. If you share it with someone they can scan it with their element camera to add you as a contact</string>
+
     <string name="choose_locale_current_locale_title">Current language</string>
     <string name="choose_locale_other_locales_title">Other available languages</string>
     <string name="choose_locale_loading_locales">Loading available languages…</string>
@@ -2672,15 +2687,17 @@
     <string name="error_opening_banned_room">Can\'t open a room where you are banned from.</string>
     <string name="room_error_not_found">Can\'t find this room. Make sure it exists.</string>
 
+    <!-- Add by QR code -->
+    <string name="share_by_text">Share by text</string>
+    <string name="cannot_dm_self">Cannot DM yourself!</string>
+    <string name="invalid_qr_code_uri">Invalid QR code (Invalid URI)!</string>
+    <string name="qr_code_not_scanned">QR code not scanned!</string>
+
     <!-- Universal link -->
     <string name="universal_link_malformed">The link was malformed</string>
     <string name="warning_room_not_created_yet">The room is not yet created. Cancel the room creation?</string>
     <string name="warning_unsaved_change">There are unsaved changes. Discard the changes?</string>
     <string name="warning_unsaved_change_discard">Discard changes</string>
 
-    <!-- Add by QR code -->
-    <string name="share_by_text">Share by text</string>
-    <string name="cannot_dm_self">Cannot DM yourself!</string>
-    <string name="invalid_qr_code_uri">Invalid QR code (Invalid URI)!</string>
-    <string name="qr_code_not_scanned">QR code not scanned!</string>
+    <string name="matrix_to_card_title">Matrix Link</string>
 </resources>
diff --git a/vector/src/main/res/xml/motion_scene_dms_fab_menu.xml b/vector/src/main/res/xml/motion_scene_dms_fab_menu.xml
deleted file mode 100644
index 8bb1c55df5..0000000000
--- a/vector/src/main/res/xml/motion_scene_dms_fab_menu.xml
+++ /dev/null
@@ -1,199 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<MotionScene xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:motion="http://schemas.android.com/apk/res-auto">
-
-    <!-- Click on main FAB: toggle -->
-    <Transition
-        motion:constraintSetEnd="@+id/constraint_set_fab_menu_open"
-        motion:constraintSetStart="@+id/constraint_set_fab_menu_close"
-        motion:duration="300"
-        motion:motionInterpolator="easeInOut">
-
-        <OnClick
-            motion:clickAction="toggle"
-            motion:targetId="@+id/dmsCreateRoomButton" />
-
-        <KeyFrameSet>
-
-            <!-- First icon goes up quickly to let room for other-->
-            <KeyPosition
-                motion:framePosition="50"
-                motion:keyPositionType="deltaRelative"
-                motion:motionTarget="@id/createDmByQrCode"
-                motion:percentX="0.8"
-                motion:percentY="0.8" />
-            <KeyPosition
-                motion:framePosition="50"
-                motion:keyPositionType="deltaRelative"
-                motion:motionTarget="@id/createDmByQrCodeLabel"
-                motion:percentX="0.9"
-                motion:percentY="0.8" />
-
-            <!-- Delay apparition of labels-->
-            <KeyAttribute
-                android:alpha="0.4"
-                motion:framePosition="80"
-                motion:motionTarget="@id/createDmByMxidLabel" />
-            <KeyAttribute
-                android:alpha="0.4"
-                motion:framePosition="80"
-                motion:motionTarget="@id/createDmByQrCodeLabel" />
-
-        </KeyFrameSet>
-    </Transition>
-
-    <ConstraintSet android:id="@+id/constraint_set_fab_menu_close">
-
-        <Constraint
-            android:id="@+id/dmsCreateRoomTouchGuard"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:background="?riotx_touch_guard_bg"
-            android:visibility="invisible" />
-
-        <!-- Sub menu item 2 -->
-        <Constraint
-            android:id="@+id/createDmByQrCode"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="bottom|end"
-            android:src="@drawable/ic_fab_add_room"
-            android:visibility="invisible"
-            motion:backgroundTint="#FFFFFF"
-            motion:fabCustomSize="48dp"
-            motion:layout_constraintBottom_toBottomOf="@+id/dmsCreateRoomButton"
-            motion:layout_constraintEnd_toEndOf="@+id/dmsCreateRoomButton"
-            motion:layout_constraintStart_toStartOf="@+id/dmsCreateRoomButton"
-            motion:layout_constraintTop_toTopOf="@+id/dmsCreateRoomButton"
-            motion:maxImageSize="26dp"
-            motion:tint="@color/black" />
-
-        <Constraint
-            android:id="@+id/createDmByQrCodeLabel"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="8dp"
-            android:text="@string/fab_menu_create_room"
-            android:visibility="invisible"
-            motion:layout_constraintBottom_toBottomOf="@+id/createDmByQrCode"
-            motion:layout_constraintEnd_toEndOf="@+id/createDmByQrCode"
-            motion:layout_constraintTop_toTopOf="@+id/createDmByQrCode" />
-
-        <!-- Sub menu item 1 -->
-        <Constraint
-            android:id="@+id/createDmByMxid"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="bottom|end"
-            android:src="@drawable/ic_fab_add_chat"
-            android:visibility="invisible"
-            motion:backgroundTint="#FFFFFF"
-            motion:fabCustomSize="48dp"
-            motion:layout_constraintBottom_toBottomOf="@+id/dmsCreateRoomButton"
-            motion:layout_constraintEnd_toEndOf="@+id/dmsCreateRoomButton"
-            motion:layout_constraintStart_toStartOf="@+id/dmsCreateRoomButton"
-            motion:layout_constraintTop_toTopOf="@+id/dmsCreateRoomButton"
-            motion:maxImageSize="29dp"
-            motion:tint="@color/black" />
-
-        <Constraint
-            android:id="@+id/createDmByMxidLabel"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="8dp"
-            android:text="@string/fab_menu_create_chat"
-            android:visibility="invisible"
-            motion:layout_constraintBottom_toBottomOf="@+id/createDmByMxid"
-            motion:layout_constraintEnd_toEndOf="@+id/createDmByMxid"
-            motion:layout_constraintTop_toTopOf="@+id/createDmByMxid" />
-
-        <!-- Menu -->
-        <Constraint
-            android:id="@+id/dmsCreateRoomButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="16dp"
-            android:layout_marginBottom="16dp"
-            android:src="@drawable/ic_fab_add"
-            motion:layout_constraintBottom_toBottomOf="parent"
-            motion:layout_constraintEnd_toEndOf="parent"
-            motion:maxImageSize="14dp" />
-
-    </ConstraintSet>
-
-    <ConstraintSet android:id="@+id/constraint_set_fab_menu_open">
-
-        <Constraint
-            android:id="@+id/dmsCreateRoomTouchGuard"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:background="?riotx_touch_guard_bg" />
-
-        <!-- Sub menu item 2 -->
-        <Constraint
-            android:id="@+id/createDmByQrCode"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="bottom|end"
-            android:layout_marginBottom="14dp"
-            android:src="@drawable/ic_fab_add_room"
-            motion:backgroundTint="#FFFFFF"
-            motion:fabCustomSize="48dp"
-            motion:layout_constraintBottom_toTopOf="@+id/createDmByMxid"
-            motion:layout_constraintEnd_toEndOf="@+id/dmsCreateRoomButton"
-            motion:layout_constraintStart_toStartOf="@+id/dmsCreateRoomButton"
-            motion:maxImageSize="26dp"
-            motion:tint="@color/black" />
-
-        <Constraint
-            android:id="@+id/createDmByQrCodeLabel"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="8dp"
-            android:text="@string/fab_menu_create_room"
-            motion:layout_constraintBottom_toBottomOf="@+id/createDmByQrCode"
-            motion:layout_constraintEnd_toStartOf="@+id/createDmByQrCode"
-            motion:layout_constraintTop_toTopOf="@+id/createDmByQrCode" />
-
-        <!-- Sub menu item 1 -->
-        <Constraint
-            android:id="@+id/createDmByMxid"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="bottom|end"
-            android:layout_marginBottom="25dp"
-            android:src="@drawable/ic_fab_add_chat"
-            motion:backgroundTint="#FFFFFF"
-            motion:fabCustomSize="48dp"
-            motion:layout_constraintBottom_toTopOf="@+id/dmsCreateRoomButton"
-            motion:layout_constraintEnd_toEndOf="@+id/dmsCreateRoomButton"
-            motion:layout_constraintStart_toStartOf="@+id/dmsCreateRoomButton"
-            motion:maxImageSize="29dp"
-            motion:tint="@color/black" />
-
-        <Constraint
-            android:id="@+id/createDmByMxidLabel"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="8dp"
-            android:text="@string/fab_menu_create_chat"
-            motion:layout_constraintBottom_toBottomOf="@+id/createDmByMxid"
-            motion:layout_constraintEnd_toStartOf="@+id/createDmByMxid"
-            motion:layout_constraintTop_toTopOf="@+id/createDmByMxid" />
-
-        <!-- Menu -->
-        <Constraint
-            android:id="@+id/dmsCreateRoomButton"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_marginEnd="16dp"
-            android:layout_marginBottom="16dp"
-            android:rotation="135"
-            android:src="@drawable/ic_fab_add"
-            motion:layout_constraintBottom_toBottomOf="parent"
-            motion:layout_constraintEnd_toEndOf="parent"
-            motion:maxImageSize="14dp" />
-
-    </ConstraintSet>
-
-</MotionScene>
\ No newline at end of file