diff --git a/app/src/main/java/io/xxlabs/messenger/data/room/model/Contact.kt b/app/src/main/java/io/xxlabs/messenger/data/room/model/Contact.kt
index 57ba12e58c2cfa4f3c65655edc9b1aef8c03bf19..45fb236e55c34b0cc41ea1920e756dd75c2f5745 100644
--- a/app/src/main/java/io/xxlabs/messenger/data/room/model/Contact.kt
+++ b/app/src/main/java/io/xxlabs/messenger/data/room/model/Contact.kt
@@ -32,8 +32,7 @@ fun Contact.formattedEmail(): String? =
     else null
 
 fun Contact.formattedPhone(flagEmoji: Boolean = false): String? =
-    if (phone.isNotBlank()) Country.toFormattedNumber(phone, flagEmoji)
-    else null
+    phone.ifBlank { null }
 
 suspend fun Contact.resolveBitmap(): Bitmap? = withContext(Dispatchers.IO) {
     BitmapResolver.getBitmap(photo)
diff --git a/app/src/main/java/io/xxlabs/messenger/requests/ui/RequestsViewModel.kt b/app/src/main/java/io/xxlabs/messenger/requests/ui/RequestsViewModel.kt
index 1624679c8806aa66e1246a3f6b56eb8bad94cbfb..c3234c566f0b253c87aaded741c178d48d333a85 100644
--- a/app/src/main/java/io/xxlabs/messenger/requests/ui/RequestsViewModel.kt
+++ b/app/src/main/java/io/xxlabs/messenger/requests/ui/RequestsViewModel.kt
@@ -366,6 +366,7 @@ class RequestsViewModel @Inject constructor(
 
     private fun showDetails(item: RequestItem) {
         when (item) {
+            is ContactRequestSearchResultItem -> showRequestDialog(item.contactRequest)
             is ContactRequestItem -> showRequestDialog(item.contactRequest)
             is SearchResultItem -> showRequestDialog(item.contactRequest)
             is GroupInviteItem -> showInvitationDialog(item.invite)
@@ -432,6 +433,7 @@ class RequestsViewModel @Inject constructor(
     private fun resendRequest(item: RequestItem) {
         when (item) {
             is ContactRequestItem -> requestsDataSource.send(item.request as ContactRequest)
+            is ContactRequestSearchResultItem -> requestsDataSource.send(item.request as ContactRequest)
             is GroupInviteItem -> invitationsDataSource.send(item.request as GroupInvitation)
         }
         onResend(item)
diff --git a/app/src/main/java/io/xxlabs/messenger/requests/ui/list/adapter/RequestItem.kt b/app/src/main/java/io/xxlabs/messenger/requests/ui/list/adapter/RequestItem.kt
index ecac901ecaab0c59ba878fa4cd6dc179d474aef4..62f2f5d5a804ab8d2afeac41a0f741ecbf642ae5 100644
--- a/app/src/main/java/io/xxlabs/messenger/requests/ui/list/adapter/RequestItem.kt
+++ b/app/src/main/java/io/xxlabs/messenger/requests/ui/list/adapter/RequestItem.kt
@@ -81,20 +81,17 @@ data class ContactRequestSearchResultItem(
     val photo: Bitmap? = null,
     val statusText: String = "Request pending",
     val statusTextColor: Int = R.color.neutral_weak,
-    val actionVisible: Boolean = true
+    val actionVisible: Boolean = true,
+    override val actionIcon: Int = R.drawable.ic_retry,
+    override val actionIconColor: Int = R.color.brand_default,
+    override val actionTextStyle: Int = R.style.request_item_retry,
+    override val actionLabel: String = if (actionVisible) appContext().getString(R.string.request_item_action_retry) else ""
 ) : RequestItem(contactRequest) {
     override val subtitle: String = statusText
     override val details: String? = null
     override val itemPhoto: Bitmap? = photo
     override val itemInitials: String = contactRequest.model.initials
     override val itemIconRes: Int? = null
-
-    // Request search results always have the "SENT" UI even if it's failed.
-    // Instead, the failed status is described in the statusText property.
-    override val actionLabel: String = if (actionVisible) appContext().getString(R.string.request_item_action_retry) else ""
-    override val actionIcon: Int = R.drawable.ic_retry
-    override val actionIconColor: Int = R.color.brand_default
-    override val actionTextStyle: Int = R.style.request_item_retry
 }
 
 data class GroupInviteItem(
diff --git a/app/src/main/java/io/xxlabs/messenger/requests/ui/list/adapter/RequestsAdapter.kt b/app/src/main/java/io/xxlabs/messenger/requests/ui/list/adapter/RequestsAdapter.kt
index abfdd264a78328e32c8a2056548625cb47abbdf9..106b94c558e11ff24bbd76d41b79c4a7cfbd73da 100644
--- a/app/src/main/java/io/xxlabs/messenger/requests/ui/list/adapter/RequestsAdapter.kt
+++ b/app/src/main/java/io/xxlabs/messenger/requests/ui/list/adapter/RequestsAdapter.kt
@@ -4,6 +4,7 @@ import android.view.ViewGroup
 import androidx.recyclerview.widget.DiffUtil
 import androidx.recyclerview.widget.ListAdapter
 import io.xxlabs.messenger.requests.ui.list.adapter.RequestsAdapter.ViewType.*
+import io.xxlabs.messenger.support.extensions.toBase64String
 
 class RequestsAdapter(
     private val listener: RequestItemListener
@@ -68,7 +69,7 @@ class RequestsAdapter(
 
 class RequestsDiffCallback : DiffUtil.ItemCallback<RequestItem>() {
     override fun areItemsTheSame(oldItem: RequestItem, newItem: RequestItem): Boolean =
-        oldItem.id.contentEquals(newItem.id)
+        oldItem.id.toBase64String() == newItem.id.toBase64String()
 
     override fun areContentsTheSame(oldItem: RequestItem, newItem: RequestItem): Boolean =
         oldItem == newItem
diff --git a/app/src/main/java/io/xxlabs/messenger/search/FactSearchFragment.kt b/app/src/main/java/io/xxlabs/messenger/search/FactSearchFragment.kt
index ad4d419009f8a661fbe64c33c4febc27f1624586..2a2f1255dd3b144ead0f4f0cd7fd58f840c04ef7 100644
--- a/app/src/main/java/io/xxlabs/messenger/search/FactSearchFragment.kt
+++ b/app/src/main/java/io/xxlabs/messenger/search/FactSearchFragment.kt
@@ -40,7 +40,7 @@ abstract class FactSearchFragment : Fragment(), Injectable {
         factoryProducer = { viewModelFactory }
     )
 
-    private val resultsAdapter: RequestsAdapter by lazy {
+    protected val resultsAdapter: RequestsAdapter by lazy {
         RequestsAdapter(requestsViewModel)
     }
     private lateinit var binding: FragmentFactSearchBinding
@@ -51,13 +51,13 @@ abstract class FactSearchFragment : Fragment(), Injectable {
         savedInstanceState: Bundle?
     ): View {
         binding = FragmentFactSearchBinding.inflate(inflater, container, false)
-        lifecycleScope.launch {
-            repeatOnLifecycle(Lifecycle.State.STARTED) {
-                getResults().collect { results ->
-                    resultsAdapter.submitList(results)
-                }
-            }
-        }
+//        lifecycleScope.launch {
+//            repeatOnLifecycle(Lifecycle.State.STARTED) {
+//                getResults().collect { results ->
+//                    resultsAdapter.submitList(results)
+//                }
+//            }
+//        }
         binding.lifecycleOwner = viewLifecycleOwner
         return binding.root
     }
@@ -113,7 +113,11 @@ class UsernameSearchFragment : FactSearchFragment() {
         searchViewModel.usernameResults
 
     override fun onSearchClicked(query: String?) {
-        searchViewModel.onUsernameSearch(query)
+        lifecycleScope.launch {
+            searchViewModel.onUsernameSearch(query).collect { results ->
+                resultsAdapter.submitList(results)
+            }
+        }
     }
 
     override fun getSearchTabUi(): FactSearchUi = searchViewModel.usernameSearchUi
@@ -124,7 +128,11 @@ class EmailSearchFragment : FactSearchFragment() {
         searchViewModel.emailResults
 
     override fun onSearchClicked(query: String?) {
-        searchViewModel.onEmailSearch(query)
+        lifecycleScope.launch {
+            searchViewModel.onEmailSearch(query).collect { results ->
+                resultsAdapter.submitList(results)
+            }
+        }
     }
 
     override fun getSearchTabUi(): FactSearchUi = searchViewModel.emailSearchUi
@@ -135,7 +143,11 @@ class PhoneSearchFragment : FactSearchFragment() {
         searchViewModel.phoneResults
 
     override fun onSearchClicked(query: String?) {
-        searchViewModel.onPhoneSearch(query)
+        lifecycleScope.launch {
+            searchViewModel.onPhoneSearch(query).collect { results ->
+                resultsAdapter.submitList(results)
+            }
+        }
     }
 
     override fun getSearchTabUi(): FactSearchUi = searchViewModel.phoneSearchUi
diff --git a/app/src/main/java/io/xxlabs/messenger/search/UserSearchViewModel.kt b/app/src/main/java/io/xxlabs/messenger/search/UserSearchViewModel.kt
index 730d2159d629368963eb9deaeda62e28b8559e1a..e5f40eaaf6edbe38ac692acc677c95f1ed6e669c 100644
--- a/app/src/main/java/io/xxlabs/messenger/search/UserSearchViewModel.kt
+++ b/app/src/main/java/io/xxlabs/messenger/search/UserSearchViewModel.kt
@@ -7,18 +7,20 @@ import android.text.SpannableString
 import android.text.Spanned
 import android.text.style.ForegroundColorSpan
 import androidx.lifecycle.*
-import bindings.Fact
 import io.xxlabs.messenger.R
 import io.xxlabs.messenger.bindings.wrapper.contact.ContactWrapperBase
 import io.xxlabs.messenger.data.data.Country
-import io.xxlabs.messenger.data.datatype.ContactRequestState
 import io.xxlabs.messenger.data.datatype.FactType
 import io.xxlabs.messenger.data.datatype.RequestStatus
+import io.xxlabs.messenger.data.room.model.Contact
 import io.xxlabs.messenger.data.room.model.ContactData
 import io.xxlabs.messenger.repository.DaoRepository
 import io.xxlabs.messenger.repository.PreferencesRepository
 import io.xxlabs.messenger.repository.base.BaseRepository
 import io.xxlabs.messenger.requests.data.contact.ContactRequestData
+import io.xxlabs.messenger.requests.data.contact.ContactRequestsRepository
+import io.xxlabs.messenger.requests.model.ContactRequest
+import io.xxlabs.messenger.requests.model.Request
 import io.xxlabs.messenger.requests.ui.list.adapter.*
 import io.xxlabs.messenger.support.appContext
 import io.xxlabs.messenger.support.toast.ToastUI
@@ -29,18 +31,17 @@ import io.xxlabs.messenger.ui.dialog.info.TwoButtonInfoDialogUI
 import io.xxlabs.messenger.ui.dialog.info.createInfoDialog
 import io.xxlabs.messenger.ui.dialog.info.createTwoButtonDialogUi
 import io.xxlabs.messenger.ui.main.countrycode.CountrySelectionListener
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
 import timber.log.Timber
 import javax.inject.Inject
+import kotlin.coroutines.coroutineContext
 
 class UserSearchViewModel @Inject constructor(
     private val repo: BaseRepository,
     private val daoRepo: DaoRepository,
-    private val preferences: PreferencesRepository
+    private val preferences: PreferencesRepository,
+    private val requestsDataSource: ContactRequestsRepository,
 ): ViewModel(){
 
     var previousTabPosition: Int = UserSearchFragment.SEARCH_USERNAME
@@ -203,6 +204,8 @@ class UserSearchViewModel @Inject constructor(
     val phoneResults: Flow<List<RequestItem>> by ::_phoneResults
     private val _phoneResults = MutableStateFlow<List<RequestItem>>(listOf())
 
+    private var searchJob: Job? = null
+
     init {
         showNewUserPopups()
     }
@@ -268,61 +271,152 @@ class UserSearchViewModel @Inject constructor(
         repo.enableDummyTraffic(enabled)
     }
 
-    fun onUsernameSearch(username: String?) {
-        username?.let {
-            val factQuery = FactQuery.UsernameQuery(it)
-            search(factQuery, _usernameResults)
-        }
+    suspend fun onUsernameSearch(username: String?): Flow<List<RequestItem>> {
+        _usernameResults.value = listOf()
+        val factQuery = FactQuery.UsernameQuery(username)
+        return search(factQuery).cancellable()
     }
 
-    fun onEmailSearch(email: String?) {
-        email?.let {
-            val factQuery = FactQuery.EmailQuery(it)
-            search(factQuery, _emailResults)
-        }
+    suspend fun onEmailSearch(email: String?): Flow<List<RequestItem>> {
+        _emailResults.value = listOf()
+        val factQuery = FactQuery.EmailQuery(email)
+        return search(factQuery).cancellable()
     }
 
-    fun onPhoneSearch(phone: String?) {
-        phone?.let {
-            val factQuery = FactQuery.PhoneQuery(it+country.countryCode)
-            search(factQuery, _phoneResults)
-        }
+    suspend fun onPhoneSearch(phone: String?): Flow<List<RequestItem>> {
+        _phoneResults.value = listOf()
+        val factQuery = FactQuery.PhoneQuery(phone + country.countryCode)
+        return search(factQuery).cancellable()
     }
 
-    private fun search(
-        factQuery: FactQuery,
-        resultsEmitter: MutableStateFlow<List<RequestItem>>
-    ) {
-        if (!isValidQuery(factQuery)) return
+    private suspend fun search(factQuery: FactQuery): Flow<List<RequestItem>> {
+        // Cancel previous searches, save a reference to this one.
+        searchJob?.cancel()
+        searchJob = coroutineContext.job
+
+        if (!isValidQuery(factQuery)) flowOf(listOf<RequestItem?>())
 
         _udSearchUi.value = searchRunningState
-        viewModelScope.launch {
-            clearPreviousResults(resultsEmitter)
-            val udResult = searchUd(factQuery)
-            val requestResults = searchRequests(factQuery)
-            val connectionResults = searchConnections(factQuery)
 
-            val remoteResults = udResult?.let {
-                listOf(udResult) + requestResults
-            } ?: requestResults
+        return combine(
+            searchUd(factQuery),
+            allRequests(),
+            allConnections()
+        ) { ud, allRequests, allConnections ->
+            val foundRequests = allRequests.matching(factQuery).toMutableSet()
+            val foundConnections = allConnections.matching(factQuery).toMutableSet()
+
+            // Add identical request to request results, if not already there.
+            allRequests.identicalTo(ud.username)?.let {
+                foundRequests.add(it)
+            }
+
+            // Add identical connection to connection results, if not already there.
+            allConnections.identicalTo(ud.username)?.let {
+                foundConnections.add(it)
+            }
+
+            val alreadyRequested = ud.username in foundRequests.map { request ->
+                request.username
+            }
 
-            val sortedResults = if (remoteResults.isEmpty()) {
-                connectionResults.ifEmpty { noResultsFor(factQuery) }
+            val alreadyAdded = ud.username in foundConnections.map { connection ->
+                connection.username
+            }
+
+            val nonConnections =
+                if (alreadyRequested || alreadyAdded) {
+                    // If the UD result's userID match a request's userID, only show the request
+                    foundRequests.toList().sortedBy { it.username }
+                } else {
+                    // Otherwise show both
+                    listOf(ud) + foundRequests.sortedBy { it.username }
+                }
+
+
+            if (nonConnections.isEmpty()) {
+                // If there's no UD result or Requests, just show the Connections with no divider.
+                foundConnections.toList().sortedBy {
+                    it.username
+                }.ifEmpty {
+                    // Show a "no results found" placeholder if there's nothing at all.
+                    noResultsFor(factQuery)
+                }
             } else {
-                if (connectionResults.isEmpty()) remoteResults
-                else remoteResults + listOf(ConnectionsDividerItem()) + connectionResults
+                if (foundConnections.isEmpty()) {
+                    // If there's no Connections, show the UD & Request results.
+                    nonConnections
+                } else {
+                    // Or show the UD results, Requests, a divider, and finally Connections.
+                    nonConnections
+                        .plus(listOf(ConnectionsDividerItem()))
+                        .plus(foundConnections.toList().sortedBy { it.username })
+                }
             }
+        }
+    }
+
+    private val RequestItem.username: String
+        get() = (request as? ContactRequest)?.model?.username ?: ""
 
-            resultsEmitter.emitResults(sortedResults)
+    private suspend fun allRequests(): Flow<List<RequestItem>> =
+        requestsDataSource.getRequests().mapNotNull { requestsList ->
+            requestsList.map {
+                it.asRequestSearchResult()
+            }
+        }.stateIn(viewModelScope)
+
+    private suspend fun allConnections() = flow {
+        val connectionsList = savedUsers().filter {
+            it.isConnection()
+        }.asConnectionsSearchResult()
+        emit(connectionsList)
+    }.stateIn(viewModelScope)
+
+    private fun List<RequestItem>.identicalTo(username: String): RequestItem? =
+        firstOrNull { it.username == username }
+
+    private fun List<RequestItem>.matching(factQuery: FactQuery): List<RequestItem> {
+        return when (factQuery.type) {
+            FactType.USERNAME -> {
+                filter {
+                    (it.request as? ContactRequest)?.model?.displayName?.contains(
+                        factQuery.fact,
+                        true
+                    ) ?: false
+                }
+            }
+            FactType.EMAIL -> {
+                filter {
+                    (it.request as? ContactRequest)?.model?.email?.contains(
+                        factQuery.fact,
+                        true
+                    ) ?: false
+                }
+            }
+            FactType.PHONE -> {
+                filter {
+                    (it.request as? ContactRequest)?.model?.phone?.contains(
+                        factQuery.fact,
+                        true
+                    ) ?: false
+                }
+            }
+            else -> listOf()
         }
     }
 
     private fun isValidQuery(factQuery: FactQuery): Boolean {
-        // Prevent users from searching (and possibly requesting) themselves.
         return with (factQuery.fact) {
-            this != repo.getStoredUsername()
-                    && this != repo.getStoredEmail()
-                    && this !=  repo.getStoredPhone()
+            if (isNullOrBlank()) {
+                // Prevent blank text
+                false
+            } else {
+                // Prevent users from searching (and possibly requesting) themselves.
+                this != repo.getStoredUsername()
+                        && this != repo.getStoredEmail()
+                        && this != repo.getStoredPhone()
+            }
         }
     }
 
@@ -334,66 +428,66 @@ class UserSearchViewModel @Inject constructor(
             text = "There are no users with that ${factQuery.type.name.lowercase()}."
         )
 
-    private suspend fun clearPreviousResults(resultsEmitter: MutableStateFlow<List<RequestItem>>) {
-        resultsEmitter.emit(listOf())
-    }
-
-    private suspend fun MutableStateFlow<List<RequestItem>>.emitResults(results: List<RequestItem>) {
-        emit(results)
-        _udSearchUi.value = searchCompleteState
-    }
-
     private suspend fun savedUsers(): List<ContactData> =
         daoRepo.getAllContacts().value()
 
     private fun ContactData.isConnection(): Boolean =
         RequestStatus.from(status) == RequestStatus.ACCEPTED
 
-    private fun ContactData.isRequest(): Boolean = !isConnection()
+    private suspend fun searchConnections(factQuery: FactQuery) = flow {
+        val results = when (factQuery.type) {
+            FactType.USERNAME -> {
+                savedUsers().filter {
+                    it.isConnection() && it.displayName.contains(factQuery.fact, true)
+                }.asConnectionsSearchResult()
 
-    private suspend fun searchConnections(factQuery: FactQuery): List<RequestItem> =
-        withContext(Dispatchers.IO) {
-            when (factQuery.type) {
-                FactType.USERNAME -> {
-                    savedUsers().filter {
-                        it.isConnection() && it.displayName.contains(factQuery.fact)
-                    }.asConnectionsSearchResult()
-                }
-                FactType.EMAIL -> {
-                    savedUsers().filter {
-                        it.isConnection() && it.email.contains(factQuery.fact)
-                    }.asConnectionsSearchResult()
-                }
-                FactType.PHONE -> {
-                    savedUsers().filter {
-                        it.isConnection() && it.phone.contains(factQuery.fact)
-                    }.asConnectionsSearchResult()
-                }
-                else -> listOf()
             }
+            FactType.EMAIL -> {
+                savedUsers().filter {
+                    it.isConnection() && it.email.contains(factQuery.fact, true)
+                }.asConnectionsSearchResult()
+
+            }
+            FactType.PHONE -> {
+                savedUsers().filter {
+                    it.isConnection() && it.phone.contains(factQuery.fact, true)
+                }.asConnectionsSearchResult()
+            }
+            else -> listOf()
         }
+        emit(results)
+    }.stateIn(viewModelScope)
 
-    private suspend fun searchRequests(factQuery: FactQuery): List<RequestItem> =
-        withContext(Dispatchers.IO) {
-            when (factQuery.type) {
-                FactType.USERNAME -> {
-                    savedUsers().filter {
-                        it.isRequest() && it.displayName.contains(factQuery.fact)
-                    }.asRequestsSearchResult()
+    private suspend fun searchRequests(factQuery: FactQuery) =
+        when (factQuery.type) {
+            FactType.USERNAME -> {
+                filterRequests {
+                    it.model.displayName.contains(factQuery.fact, true)
                 }
-                FactType.EMAIL -> {
-                    savedUsers().filter {
-                        it.isRequest() && it.email.contains(factQuery.fact)
-                    }.asRequestsSearchResult()
+            }
+            FactType.EMAIL -> {
+                filterRequests {
+                    it.model.email.contains(factQuery.fact, true)
                 }
-                FactType.PHONE -> {
-                    savedUsers().filter {
-                        it.isRequest() && it.phone.contains(factQuery.fact)
-                    }.asRequestsSearchResult()
+            }
+            FactType.PHONE -> {
+                filterRequests {
+                    it.model.phone.contains(
+                        Country.toFormattedNumber(factQuery.fact, false) ?: factQuery.fact
+                    )
                 }
-                else -> listOf()
             }
-        }
+            else -> flow { listOf<RequestItem>() }
+        }.stateIn(viewModelScope)
+
+    private suspend fun filterRequests(match: (contactRequest: ContactRequest) -> Boolean) =
+        requestsDataSource.getRequests().map { requestsList ->
+            requestsList.filter {
+                match(it)
+            }.map {
+                it.asRequestSearchResult()
+            }
+        }.flowOn(Dispatchers.IO)
 
     private suspend fun List<ContactData>.asConnectionsSearchResult(): List<RequestItem> =
         map {
@@ -404,22 +498,28 @@ class UserSearchViewModel @Inject constructor(
             )
         }
 
-    private suspend fun List<ContactData>.asRequestsSearchResult(): List<RequestItem> =
-        map {
-            ContactRequestSearchResultItem(
-                contactRequest = ContactRequestData(it),
-                photo = resolveBitmap(it.photo),
-                statusText = it.statusText(),
-                statusTextColor = it.statusTextColor(),
-                actionVisible= it.actionVisible()
-            )
-        }
+    private suspend fun ContactRequest.asRequestSearchResult(): RequestItem =
+        ContactRequestSearchResultItem(
+            contactRequest = this,
+            photo = resolveBitmap(model.photo),
+            statusText = model.statusText(),
+            statusTextColor = model.statusTextColor(),
+            actionVisible = model.actionVisible(),
+            actionIcon = model.actionIcon(),
+            actionIconColor = model.actionIconColor(),
+            actionTextStyle = model.actionTextStyle(),
+            actionLabel = model.actionLabel()
+        )
 
-    private fun ContactData.statusText(): String {
+    private fun Contact.statusText(): String {
         return when (RequestStatus.from(status)) {
             RequestStatus.SENT,
             RequestStatus.VERIFIED,
-            RequestStatus.RESET_SENT -> "Request pending"
+            RequestStatus.RESET_SENT,
+            RequestStatus.RESENT,
+            RequestStatus.VERIFYING,
+            RequestStatus.HIDDEN,
+            RequestStatus.SENDING -> "Request pending"
 
             RequestStatus.SEND_FAIL,
             RequestStatus.CONFIRM_FAIL,
@@ -430,7 +530,7 @@ class UserSearchViewModel @Inject constructor(
         }
     }
 
-    private fun ContactData.statusTextColor(): Int {
+    private fun Contact.statusTextColor(): Int {
         return when (RequestStatus.from(status)) {
             RequestStatus.SEND_FAIL,
             RequestStatus.CONFIRM_FAIL,
@@ -441,35 +541,71 @@ class UserSearchViewModel @Inject constructor(
         }
     }
 
-    private fun ContactData.actionVisible(): Boolean {
+    private fun Contact.actionVisible(): Boolean {
         return when (RequestStatus.from(status)) {
-            RequestStatus.VERIFIED -> false
+            RequestStatus.VERIFIED, RequestStatus.VERIFYING, RequestStatus.HIDDEN -> false
             else -> true
         }
     }
 
+    private fun Contact.actionIcon(): Int {
+        return when (RequestStatus.from(status)) {
+            RequestStatus.RESENT -> R.drawable.ic_check_green
+            else -> R.drawable.ic_retry
+        }
+    }
+
+    private fun Contact.actionIconColor(): Int {
+        return when (RequestStatus.from(status)) {
+            RequestStatus.RESENT ->  R.color.accent_success
+            else -> R.color.brand_default
+        }
+    }
+
+    private fun Contact.actionTextStyle(): Int {
+        return when (RequestStatus.from(status)) {
+            RequestStatus.RESENT -> R.drawable.ic_check_green
+            else -> R.style.request_item_resent
+        }
+    }
+
+    private fun Contact.actionLabel(): String {
+        return when (RequestStatus.from(status)) {
+            RequestStatus.RESENT -> appContext().getString(R.string.request_item_action_resent)
+            else -> appContext().getString(R.string.request_item_action_retry)
+        }
+    }
+
+
     private suspend fun resolveBitmap(data: ByteArray?): Bitmap? = withContext(Dispatchers.IO) {
         BitmapResolver.getBitmap(data)
     }
 
-    private suspend fun searchUd(factQuery: FactQuery): RequestItem? {
-        return try {
+    private suspend fun searchUd(factQuery: FactQuery) = flow {
+        val result = try {
             val udResult = repo.searchUd(factQuery.fact, factQuery.type).value()
             udResult.second?.let { // Error message
                 if (it.isNotEmpty()) {
                     if (!it.contains("no results found", true)) {
                         showToast(it)
                     }
+                    _udSearchUi.value = searchCompleteState
                     noResultPlaceholder(factQuery)
                 } else { // Search result
+                    _udSearchUi.value = searchCompleteState
                     udResult.first?.asSearchResult() ?: noResultPlaceholder(factQuery)
                 }
-            } ?: udResult.first?.asSearchResult() ?: noResultPlaceholder(factQuery)
+            } ?: run {
+                _udSearchUi.value = searchCompleteState
+                udResult.first?.asSearchResult() ?: noResultPlaceholder(factQuery)
+            }
         } catch (e: Exception) {
             e.message?.let { showToast(it) }
+            _udSearchUi.value = searchCompleteState
             noResultPlaceholder(factQuery)
         }
-    }
+        emit(result)
+    }.stateIn(viewModelScope)
 
     private fun ContactWrapperBase.asSearchResult(): RequestItem {
         // ContactWrapperBase -> ContactRequestData
@@ -506,6 +642,7 @@ class UserSearchViewModel @Inject constructor(
     }
 
     private fun onCancelSearchClicked() {
+        searchJob?.cancel()
         _udSearchUi.value = searchCompleteState
     }
 
@@ -536,18 +673,18 @@ private sealed class FactQuery {
     abstract val fact: String
     abstract val type: FactType
 
-    class UsernameQuery(query: String): FactQuery() {
-        override val fact: String = query
+    class UsernameQuery(query: String?): FactQuery() {
+        override val fact: String = query ?: ""
         override val type: FactType = FactType.USERNAME
     }
 
-    class EmailQuery(query: String): FactQuery() {
-        override val fact: String = query
+    class EmailQuery(query: String?): FactQuery() {
+        override val fact: String = query ?: ""
         override val type: FactType = FactType.EMAIL
     }
 
-    class PhoneQuery(query: String): FactQuery() {
-        override val fact: String = query
+    class PhoneQuery(query: String?): FactQuery() {
+        override val fact: String = query ?: ""
         override val type: FactType = FactType.PHONE
     }
 }
\ No newline at end of file
diff --git a/app/src/main/res/drawable/white_rounded_square_outline.xml b/app/src/main/res/drawable/white_rounded_square_outline.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5e52b28c38a41e2e39df01c33ca7e1d0d4c84557
--- /dev/null
+++ b/app/src/main/res/drawable/white_rounded_square_outline.xml
@@ -0,0 +1,5 @@
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <corners android:radius="@dimen/spacing_30"/>
+    <stroke android:width="2dp" android:color="@color/white"/>
+</shape>
\ No newline at end of file
diff --git a/app/src/main/res/layout/component_toolbar_generic.xml b/app/src/main/res/layout/component_toolbar_generic.xml
index 91641055cabd1160616459cb6ca69aa609df520f..e855c5b92bbae0e50ed304da4aa2a285e97a4d13 100644
--- a/app/src/main/res/layout/component_toolbar_generic.xml
+++ b/app/src/main/res/layout/component_toolbar_generic.xml
@@ -120,6 +120,7 @@
             android:layout_width="0dp"
             android:layout_height="1dp"
             android:background="@color/neutral_line"
+            android:visibility="gone"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent" />
diff --git a/app/src/main/res/layout/fragment_chats_list.xml b/app/src/main/res/layout/fragment_chats_list.xml
index a9089f9e9c6ebcdf648e3b3b2262c3af9533a3e5..773edfdebd1a6778471f403db8527091bb8d34b2 100755
--- a/app/src/main/res/layout/fragment_chats_list.xml
+++ b/app/src/main/res/layout/fragment_chats_list.xml
@@ -140,6 +140,7 @@
             android:layout_height="1dp"
             android:layout_marginTop="@dimen/spacing_12"
             android:background="@color/neutral_line"
+            android:visibility="gone"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toBottomOf="@id/chatsMenu" />
diff --git a/app/src/main/res/layout/fragment_user_search.xml b/app/src/main/res/layout/fragment_user_search.xml
index 95682ea6f17450217d6512bc57cd9a91dcf97536..e66bfcd667569654c01fe51f507ed38361570979 100644
--- a/app/src/main/res/layout/fragment_user_search.xml
+++ b/app/src/main/res/layout/fragment_user_search.xml
@@ -99,8 +99,8 @@
             android:id="@+id/fadeView"
             android:layout_width="0dp"
             android:layout_height="0dp"
-            android:alpha="0.85"
-            android:background="@color/neutral_white"
+            android:alpha=".8"
+            android:background="@color/black"
             android:visibility="@{ui.isSearching}"
             android:elevation="5dp"
             app:layout_constraintBottom_toBottomOf="parent"
@@ -116,12 +116,13 @@
             android:indeterminate="true"
             android:visibility="@{ui.isSearching}"
             android:elevation="5dp"
+            android:indeterminateTint="@color/white"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
             app:layout_constraintTop_toTopOf="parent" />
 
-        <io.xxlabs.messenger.support.view.SingleClickButton
+        <Button
             android:id="@+id/userSearchCancelButton"
             style="@style/dialog_button"
             android:layout_width="0dp"
@@ -131,6 +132,8 @@
             android:stateListAnimator="@null"
             android:text="@string/cancel"
             android:visibility="@{ui.isSearching}"
+            android:background="@drawable/white_rounded_square_outline"
+            android:textColor="@color/white"
             app:layout_constraintBottom_toBottomOf="parent"
             app:layout_constraintEnd_toEndOf="parent"
             app:layout_constraintStart_toStartOf="parent"
diff --git a/app/src/main/res/layout/list_item_request_search_result.xml b/app/src/main/res/layout/list_item_request_search_result.xml
index 6dcf6c880a13c4dc0eab50d1f8818468c0295fa9..84d3ecc71665c8514dd0181fb6e9690611c05578 100644
--- a/app/src/main/res/layout/list_item_request_search_result.xml
+++ b/app/src/main/res/layout/list_item_request_search_result.xml
@@ -69,7 +69,7 @@
             android:layout_marginHorizontal="0dp"
             android:drawablePadding="4dp"
             android:onClick="@{() -> listener.onActionClicked(ui)}"
-            android:visibility="@{ui.actionLabel}"
+            android:visibility="@{ui.actionVisible}"
             android:singleLine="false"
             android:gravity="center_vertical|end"
             app:actionIcon="@{ui.actionIcon}"