Add checkbox to filter contacts with MatrixId only

This commit is contained in:
Benoit Marty 2020-07-08 21:52:14 +02:00
parent 1c733e6661
commit 6ceac578a3
7 changed files with 108 additions and 57 deletions

View file

@ -20,4 +20,5 @@ import im.vector.riotx.core.platform.VectorViewModelAction
sealed class PhoneBookAction : VectorViewModelAction {
data class FilterWith(val filter: String) : PhoneBookAction()
data class OnlyBoundContacts(val onlyBoundContacts: Boolean) : PhoneBookAction()
}

View file

@ -52,11 +52,11 @@ class PhoneBookController @Inject constructor(
override fun buildModels() {
val currentState = state ?: return
val hasSearch = currentState.searchTerm.isNotBlank()
val hasSearch = currentState.searchTerm.isNotEmpty()
when (val asyncMappedContacts = currentState.mappedContacts) {
is Uninitialized -> renderEmptyState(false)
is Loading -> renderLoading()
is Success -> renderSuccess(currentState.filteredMappedContacts, hasSearch)
is Success -> renderSuccess(currentState.filteredMappedContacts, hasSearch, currentState.onlyBoundContacts)
is Fail -> renderFailure(asyncMappedContacts.error)
}
}
@ -75,49 +75,54 @@ class PhoneBookController @Inject constructor(
}
private fun renderSuccess(mappedContacts: List<ContactModel>,
hasSearch: Boolean) {
hasSearch: Boolean,
onlyBoundContacts: Boolean) {
if (mappedContacts.isEmpty()) {
renderEmptyState(hasSearch)
} else {
renderContacts(mappedContacts)
renderContacts(mappedContacts, onlyBoundContacts)
}
}
private fun renderContacts(mappedContacts: List<ContactModel>) {
private fun renderContacts(mappedContacts: List<ContactModel>, onlyBoundContacts: Boolean) {
for (mappedContact in mappedContacts) {
contactItem {
id(mappedContact.id)
contact(mappedContact)
avatarRenderer(avatarRenderer)
}
mappedContact.emails.forEach {
contactDetailItem {
id("$mappedContact.id${it.email}")
threePid(it.email)
matrixId(it.matrixId)
clickListener {
if (it.matrixId != null) {
callback?.onMatrixIdClick(it.matrixId)
} else {
callback?.onThreePidClick(ThreePid.Email(it.email))
mappedContact.emails
.filter { !onlyBoundContacts || it.matrixId != null }
.forEach {
contactDetailItem {
id("$mappedContact.id${it.email}")
threePid(it.email)
matrixId(it.matrixId)
clickListener {
if (it.matrixId != null) {
callback?.onMatrixIdClick(it.matrixId)
} else {
callback?.onThreePidClick(ThreePid.Email(it.email))
}
}
}
}
}
}
mappedContact.msisdns.forEach {
contactDetailItem {
id("$mappedContact.id${it.phoneNumber}")
threePid(it.phoneNumber)
matrixId(it.matrixId)
clickListener {
if (it.matrixId != null) {
callback?.onMatrixIdClick(it.matrixId)
} else {
callback?.onThreePidClick(ThreePid.Msisdn(it.phoneNumber))
mappedContact.msisdns
.filter { !onlyBoundContacts || it.matrixId != null }
.forEach {
contactDetailItem {
id("$mappedContact.id${it.phoneNumber}")
threePid(it.phoneNumber)
matrixId(it.matrixId)
clickListener {
if (it.matrixId != null) {
callback?.onMatrixIdClick(it.matrixId)
} else {
callback?.onThreePidClick(ThreePid.Msisdn(it.phoneNumber))
}
}
}
}
}
}
}
}

View file

@ -18,8 +18,10 @@ package im.vector.riotx.features.userdirectory
import android.os.Bundle
import android.view.View
import androidx.core.view.isVisible
import com.airbnb.mvrx.activityViewModel
import com.airbnb.mvrx.withState
import com.jakewharton.rxbinding3.widget.checkedChanges
import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.session.identity.ThreePid
import im.vector.matrix.android.api.session.user.model.User
@ -50,9 +52,18 @@ class PhoneBookFragment @Inject constructor(
sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java)
setupRecyclerView()
setupFilterView()
setupOnlyBoundContactsView()
setupCloseView()
}
private fun setupOnlyBoundContactsView() {
phoneBookOnlyBoundContacts.checkedChanges()
.subscribe {
phoneBookViewModel.handle(PhoneBookAction.OnlyBoundContacts(it))
}
.disposeOnDestroyView()
}
private fun setupFilterView() {
phoneBookFilter
.textChanges()
@ -81,8 +92,9 @@ class PhoneBookFragment @Inject constructor(
}
}
override fun invalidate() = withState(phoneBookViewModel) {
phoneBookController.setData(it)
override fun invalidate() = withState(phoneBookViewModel) { state ->
phoneBookOnlyBoundContacts.isVisible = state.isBoundRetrieved
phoneBookController.setData(state)
}
override fun onMatrixIdClick(matrixId: String) {

View file

@ -37,7 +37,9 @@ import im.vector.riotx.core.platform.EmptyViewEvents
import im.vector.riotx.core.platform.VectorViewModel
import im.vector.riotx.features.createdirect.CreateDirectRoomActivity
import im.vector.riotx.features.invite.InviteUsersToRoomActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import timber.log.Timber
private typealias PhoneBookSearch = String
@ -71,13 +73,12 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted
private var allContacts: List<ContactModel> = emptyList()
private var mappedContacts: List<ContactModel> = emptyList()
private var foundThreePid: List<FoundThreePid> = emptyList()
init {
loadContacts()
selectSubscribe(PhoneBookViewState::searchTerm) {
updateState()
selectSubscribe(PhoneBookViewState::searchTerm, PhoneBookViewState::onlyBoundContacts) { _, _ ->
updateFilteredMappedContacts()
}
}
@ -88,7 +89,7 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted
)
}
viewModelScope.launch {
viewModelScope.launch(Dispatchers.IO) {
allContacts = contactsDataSource.getContacts()
mappedContacts = allContacts
@ -99,7 +100,7 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted
}
performLookup(allContacts)
updateState()
updateFilteredMappedContacts()
}
}
@ -111,24 +112,23 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted
}
session.identityService().lookUp(threePids, object : MatrixCallback<List<FoundThreePid>> {
override fun onFailure(failure: Throwable) {
// Ignore?
// Ignore
Timber.w(failure, "Unable to perform the lookup")
}
override fun onSuccess(data: List<FoundThreePid>) {
foundThreePid = data
mappedContacts = allContacts.map { contactModel ->
contactModel.copy(
emails = contactModel.emails.map { email ->
email.copy(
matrixId = foundThreePid
matrixId = data
.firstOrNull { foundThreePid -> foundThreePid.threePid.value == email.email }
?.matrixId
)
},
msisdns = contactModel.msisdns.map { msisdn ->
msisdn.copy(
matrixId = foundThreePid
matrixId = data
.firstOrNull { foundThreePid -> foundThreePid.threePid.value == msisdn.phoneNumber }
?.matrixId
)
@ -136,15 +136,25 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted
)
}
updateState()
setState {
copy(
isBoundRetrieved = true
)
}
updateFilteredMappedContacts()
}
})
}
}
private fun updateState() = withState { state ->
private fun updateFilteredMappedContacts() = withState { state ->
val filteredMappedContacts = mappedContacts
.filter { it.displayName.contains(state.searchTerm, true) }
.filter { contactModel ->
!state.onlyBoundContacts
|| contactModel.emails.any { it.matrixId != null } || contactModel.msisdns.any { it.matrixId != null }
}
setState {
copy(
@ -155,10 +165,19 @@ class PhoneBookViewModel @AssistedInject constructor(@Assisted
override fun handle(action: PhoneBookAction) {
when (action) {
is PhoneBookAction.FilterWith -> handleFilterWith(action)
is PhoneBookAction.FilterWith -> handleFilterWith(action)
is PhoneBookAction.OnlyBoundContacts -> handleOnlyBoundContacts(action)
}.exhaustive
}
private fun handleOnlyBoundContacts(action: PhoneBookAction.OnlyBoundContacts) {
setState {
copy(
onlyBoundContacts = action.onlyBoundContacts
)
}
}
private fun handleFilterWith(action: PhoneBookAction.FilterWith) {
setState {
copy(

View file

@ -22,14 +22,14 @@ import com.airbnb.mvrx.MvRxState
import im.vector.riotx.core.contacts.ContactModel
data class PhoneBookViewState(
val searchTerm: String = "",
// All the contacts on the phone
val mappedContacts: Async<List<ContactModel>> = Loading(),
val filteredMappedContacts: List<ContactModel> = emptyList()
/*
val knownUsers: Async<PagedList<User>> = Uninitialized,
val directoryUsers: Async<List<User>> = Uninitialized,
val selectedUsers: Set<User> = emptySet(),
val createAndInviteState: Async<String> = Uninitialized,
val filterKnownUsersValue: Option<String> = Option.empty()
*/
// Use to filter contacts by display name
val searchTerm: String = "",
// Tru to display only bound contacts with their bound 2pid
val onlyBoundContacts: Boolean = false,
// All contacts, filtered by searchTerm and onlyBoundContacts
val filteredMappedContacts: List<ContactModel> = emptyList(),
// True when the identity service has return some data
val isBoundRetrieved: Boolean = false
) : MvRxState

View file

@ -79,6 +79,20 @@
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/phoneBookOnlyBoundContacts"
android:layout_width="0dp"
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"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer"
tools:visibility="visible" />
<View
android:id="@+id/phoneBookFilterDivider"
android:layout_width="0dp"
@ -87,13 +101,12 @@
android:background="?attr/vctr_list_divider_color"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/phoneBookFilterContainer" />
app:layout_constraintTop_toBottomOf="@+id/phoneBookOnlyBoundContacts" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/phoneBookRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="16dp"
android:fastScrollEnabled="true"
android:overScrollMode="always"
android:scrollbars="vertical"

View file

@ -6,8 +6,9 @@
android:layout_height="wrap_content"
android:background="?riotx_background"
android:foreground="?attr/selectableItemBackground"
android:minHeight="72dp"
android:padding="8dp">
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp">
<ImageView
android:id="@+id/contactAvatar"