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}"