Fix up search with contacts

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2020-02-01 23:02:14 +01:00
parent ee570224d9
commit 6c73b6f70c
No known key found for this signature in database
GPG Key ID: CDE0BBD2738C4CC0
7 changed files with 97 additions and 85 deletions

View File

@ -20,24 +20,11 @@
*
*/
package com.nextcloud.talk.newarch.features.contactsflow.source
package com.nextcloud.talk.newarch.features.contactsflow
import com.nextcloud.talk.newarch.features.contactsflow.contacts.ContactsViewSource
import com.otaliastudios.elements.Source
import com.otaliastudios.elements.extensions.ListSource
import com.nextcloud.talk.models.json.participants.Participant
class FixedListSource(list: List<Any>, elementType: Int) : ListSource<Any>(list, elementType) {
override fun areContentsTheSame(first: Any, second: Any): Boolean {
return true
}
override fun <E : Any> areItemsTheSame(own: Any, dependency: Source<E>, other: E?): Boolean {
return true
}
override fun dependsOn(source: Source<*>): Boolean {
return source is ContactsViewSource
}
}
data class ParticipantElement(
val data: Any,
val elementType: Int
)

View File

@ -28,6 +28,7 @@ import androidx.core.view.isVisible
import coil.api.load
import com.nextcloud.talk.R
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.newarch.features.contactsflow.ParticipantElement
import com.nextcloud.talk.newarch.local.models.getCredentials
import com.nextcloud.talk.newarch.services.GlobalService
import com.nextcloud.talk.newarch.utils.ElementPayload
@ -78,13 +79,14 @@ open class ContactPresenter<T : Any>(context: Context, onElementClick: ((Page, H
super.onBind(page, holder, element, payloads)
if (element.type == ParticipantElementType.PARTICIPANT.ordinal || element.type == ParticipantElementType.PARTICIPANT_SELECTED.ordinal) {
val participant = element.data as Participant?
val participantElement = element.data as ParticipantElement
val participant = participantElement.data as Participant
val user = globalService.currentUserLiveData.value
holder.itemView.checkedImageView?.isVisible = participant?.selected == true
holder.itemView.checkedImageView?.isVisible = participant.selected == true
if (!payloads.contains(ElementPayload.SELECTION_TOGGLE)) {
participant?.displayName?.let {
participant.displayName?.let {
if (element.type == ParticipantElementType.PARTICIPANT_SELECTED.ordinal) {
holder.itemView.participantNameTextView.text = it.substringBefore(" ", it)
} else {
@ -97,7 +99,7 @@ open class ContactPresenter<T : Any>(context: Context, onElementClick: ((Page, H
holder.itemView.clearImageView?.load(Images().getImageWithBackground(context, R.drawable.ic_baseline_clear_24, R.color.bg_selected_participant_clear_icon, R.color.white))
when (participant?.source) {
when (participant.source) {
"users" -> {
when (participant.type) {
Participant.ParticipantType.GUEST, Participant.ParticipantType.GUEST_AS_MODERATOR, Participant.ParticipantType.USER_FOLLOWING_LINK -> {
@ -127,7 +129,7 @@ open class ContactPresenter<T : Any>(context: Context, onElementClick: ((Page, H
} else if (element.type == ParticipantElementType.PARTICIPANT_FOOTER.ordinal) {
holder.itemView.messageTextView.text = (element.data as FooterSource.Data<*, *>).footer.toString()
} else if (element.type == ParticipantElementType.PARTICIPANT_NEW_GROUP.ordinal) {
val pairData = element.data as Pair<*, *>
val pairData = (element.data as ParticipantElement).data as Pair<*, *>
holder.itemView.participantNameTextView.text = pairData.first as CharSequence
holder.itemView.avatarImageView.load(Images().getImageWithBackground(context, pairData.second as Int))
} else {

View File

@ -39,7 +39,6 @@ import com.nextcloud.talk.controllers.ChatController
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.newarch.features.contactsflow.ContactsViewOperationState
import com.nextcloud.talk.newarch.features.contactsflow.groupconversation.GroupConversationView
import com.nextcloud.talk.newarch.features.contactsflow.source.FixedListSource
import com.nextcloud.talk.newarch.features.search.DebouncingTextWatcher
import com.nextcloud.talk.newarch.mvvm.BaseView
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
@ -84,8 +83,7 @@ class ContactsView(private val bundle: Bundle? = null) : BaseView() {
// todo - change empty state magic
val participantsAdapterBuilder = Adapter.builder(this)
//.addSource(FixedListSource(listOf(Pair(context.getString(R.string.nc_join_via_link), R.drawable.ic_link_white_24px)), ParticipantElementType.PARTICIPANT_JOIN_VIA_LINK.ordinal))
.addSource(ContactsViewSource(data = viewModel.contactsLiveData, elementType = ParticipantElementType.PARTICIPANT.ordinal))
.addSource(ContactsViewSource(data = viewModel.contactsLiveData))
.addSource(ContactsHeaderSource(activity as Context, ParticipantElementType.PARTICIPANT_HEADER.ordinal))
.addSource(ContactsViewFooterSource(activity as Context, ParticipantElementType.PARTICIPANT_FOOTER.ordinal))
.addPresenter(ContactPresenter(activity as Context, ::onElementClick))
@ -97,14 +95,10 @@ class ContactsView(private val bundle: Bundle? = null) : BaseView() {
})
.setAutoScrollMode(Adapter.AUTOSCROLL_POSITION_0, true)
if (!hasToken) {
participantsAdapterBuilder.addSource(FixedListSource(listOf(Pair(context.getString(R.string.nc_new_group), R.drawable.ic_people_group_white_24px)), ParticipantElementType.PARTICIPANT_NEW_GROUP.ordinal))
}
participantsAdapter = participantsAdapterBuilder.into(view.selectedParticipantsRecyclerView)
selectedParticipantsAdapter = Adapter.builder(this)
.addSource(ContactsViewSource(data = viewModel.selectedParticipantsLiveData, elementType = ParticipantElementType.PARTICIPANT_SELECTED.ordinal, loadingIndicatorsEnabled = false, errorIndicatorEnabled = false, emptyIndicatorEnabled = false))
.addSource(ContactsViewSource(data = viewModel.selectedParticipantsLiveData, loadingIndicatorsEnabled = false, errorIndicatorEnabled = false, emptyIndicatorEnabled = false))
.addPresenter(ContactPresenter(activity as Context, ::onElementClick))
.setAutoScrollMode(Adapter.AUTOSCROLL_POSITION_ANY, true)
.into(view.selectedParticipantsRecyclerView)
@ -241,8 +235,10 @@ class ContactsView(private val bundle: Bundle? = null) : BaseView() {
override fun onFloatingActionButtonClick() {
if (hasToken) {
val conversationToken = bundle?.getString(BundleKeys.KEY_CONVERSATION_TOKEN)
conversationToken?.let {
viewModel.selectedParticipantsLiveData.value?.let { participants -> viewModel.addParticipants(it, participants) }
conversationToken?.let { conversationToken ->
viewModel.selectedParticipantsLiveData.value?.let { participantElements ->
viewModel.addParticipants(conversationToken, participantElements.map { it.data as Participant })
}
}
}
}

View File

@ -25,34 +25,35 @@ package com.nextcloud.talk.newarch.features.contactsflow.contacts
import android.content.Context
import com.nextcloud.talk.R
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.newarch.features.contactsflow.ParticipantElement
import com.otaliastudios.elements.Page
import com.otaliastudios.elements.Source
import com.otaliastudios.elements.extensions.FooterSource
class ContactsViewFooterSource(private val context: Context, private val elementType: Int) : FooterSource<Participant, String>() {
private var lastAnchor: Participant? = null
class ContactsViewFooterSource(private val context: Context, private val elementType: Int) : FooterSource<ParticipantElement, String>() {
private var lastAnchor: ParticipantElement? = null
override fun dependsOn(source: Source<*>): Boolean {
return source is ContactsViewSource
}
override fun areItemsTheSame(first: Data<Participant, String>, second: Data<Participant, String>): Boolean {
override fun areItemsTheSame(first: Data<ParticipantElement, String>, second: Data<ParticipantElement, String>): Boolean {
return first == second
}
override fun getElementType(data: Data<Participant, String>) = elementType
override fun getElementType(data: Data<ParticipantElement, String>) = elementType
override fun computeFooters(page: Page, list: List<Participant>): List<Data<Participant, String>> {
override fun computeFooters(page: Page, list: List<ParticipantElement>): List<Data<ParticipantElement, String>> {
lastAnchor = null
val results = arrayListOf<Data<Participant, String>>()
val results = arrayListOf<Data<ParticipantElement, String>>()
lastAnchor = if (list.isNotEmpty()) {
val participant = list.takeLast(1)[0]
val participantElement = list.takeLast(1)[0]
if (lastAnchor == null || lastAnchor != participant) {
results.add(Data(participant, context.getString(R.string.nc_search_for_more)))
if (lastAnchor == null || lastAnchor != participantElement) {
results.add(Data(participantElement, context.getString(R.string.nc_search_for_more)))
}
participant
participantElement
} else {
null
}

View File

@ -25,53 +25,58 @@ package com.nextcloud.talk.newarch.features.contactsflow.contacts
import android.content.Context
import com.nextcloud.talk.R
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.newarch.features.contactsflow.ParticipantElement
import com.otaliastudios.elements.Page
import com.otaliastudios.elements.Source
import com.otaliastudios.elements.extensions.HeaderSource
class ContactsHeaderSource(private val context: Context, private val elementType: Int) : HeaderSource<Participant, String>() {
class ContactsHeaderSource(private val context: Context, private val elementType: Int) : HeaderSource<ParticipantElement, String>() {
// Store the last header that was added, even if it belongs to a previous page.
private var headersAlreadyAdded = mutableListOf<String>()
override fun dependsOn(source: Source<*>) = source is ContactsViewSource
override fun computeHeaders(page: Page, list: List<Participant>): List<Data<Participant, String>> {
val results = arrayListOf<Data<Participant, String>>()
override fun computeHeaders(page: Page, list: List<ParticipantElement>): List<Data<ParticipantElement, String>> {
val results = arrayListOf<Data<ParticipantElement, String>>()
headersAlreadyAdded = mutableListOf()
for (participant in list) {
val header = when (participant.source) {
"users" -> {
context.getString(R.string.nc_contacts)
}
"groups" -> {
context.getString(R.string.nc_groups)
}
"emails" -> {
context.getString(R.string.nc_emails)
}
"circles" -> {
context.getString(R.string.nc_circles)
}
else -> {
context.getString(R.string.nc_others)
}
}
for (participantElement in list) {
if (participantElement.data is Participant) {
val participant = participantElement.data
val header = when (participant.source) {
"users" -> {
context.getString(R.string.nc_contacts)
}
"groups" -> {
context.getString(R.string.nc_groups)
}
"emails" -> {
context.getString(R.string.nc_emails)
}
"circles" -> {
context.getString(R.string.nc_circles)
}
else -> {
context.getString(R.string.nc_others)
if (!headersAlreadyAdded.contains(header)) {
results.add(Data(participant, header))
headersAlreadyAdded.add(header)
}
}
}
if (!headersAlreadyAdded.contains(header)) {
results.add(Data(participantElement, header))
headersAlreadyAdded.add(header)
}
}
}
return results
}
override fun getElementType(data: Data<Participant, String>): Int {
override fun getElementType(data: Data<ParticipantElement, String>): Int {
return elementType
}
override fun areItemsTheSame(first: Data<Participant, String>, second: Data<Participant, String>): Boolean {
override fun areItemsTheSame(first: Data<ParticipantElement, String>, second: Data<ParticipantElement, String>): Boolean {
return first == second
}
}

View File

@ -27,6 +27,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.distinctUntilChanged
import androidx.lifecycle.viewModelScope
import com.nextcloud.talk.R
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.models.json.conversations.ConversationOverall
import com.nextcloud.talk.models.json.participants.AddParticipantOverall
@ -39,6 +40,7 @@ import com.nextcloud.talk.newarch.domain.usecases.GetContactsUseCase
import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
import com.nextcloud.talk.newarch.features.contactsflow.ContactsViewOperationState
import com.nextcloud.talk.newarch.features.contactsflow.ContactsViewOperationStateWrapper
import com.nextcloud.talk.newarch.features.contactsflow.ParticipantElement
import com.nextcloud.talk.newarch.features.conversationslist.ConversationsListView
import com.nextcloud.talk.newarch.services.GlobalService
import kotlinx.coroutines.runBlocking
@ -51,10 +53,10 @@ class ContactsViewModel constructor(
private val addParticipantToConversationUseCase: AddParticipantToConversationUseCase,
val globalService: GlobalService
) : BaseViewModel<ConversationsListView>(application) {
private val selectedParticipants = mutableListOf<Participant>()
val selectedParticipantsLiveData: MutableLiveData<List<Participant>> = MutableLiveData()
private val _contacts: MutableLiveData<List<Participant>> = MutableLiveData()
val contactsLiveData: LiveData<List<Participant>> = _contacts
private val selectedParticipants = mutableListOf<ParticipantElement>()
val selectedParticipantsLiveData: MutableLiveData<List<ParticipantElement>> = MutableLiveData()
private val _contacts: MutableLiveData<List<ParticipantElement>> = MutableLiveData()
val contactsLiveData: LiveData<List<ParticipantElement>> = _contacts
private val _operationState = MutableLiveData(ContactsViewOperationStateWrapper(ContactsViewOperationState.WAITING, null, null))
val operationState: LiveData<ContactsViewOperationStateWrapper> = _operationState.distinctUntilChanged()
@ -79,12 +81,12 @@ class ContactsViewModel constructor(
}
fun selectParticipant(participant: Participant) {
selectedParticipants.add(participant)
selectedParticipants.add(ParticipantElement(participant, ParticipantElementType.PARTICIPANT_SELECTED.ordinal))
selectedParticipantsLiveData.postValue(selectedParticipants)
}
fun unselectParticipant(participant: Participant) {
selectedParticipants.remove(participant)
selectedParticipants.remove(ParticipantElement(participant, ParticipantElementType.PARTICIPANT_SELECTED.ordinal))
selectedParticipantsLiveData.postValue(selectedParticipants)
}
@ -146,14 +148,24 @@ class ContactsViewModel constructor(
it.displayName!!.toLowerCase()
}))
val selectedUserIds = selectedParticipants.map { it.userId }
for (participant in sortedList) {
if (participant.userId in selectedUserIds) {
participant.selected = true
val selectedUserIds = selectedParticipants.map { (it.data as Participant).userId }
val participantElementsList: MutableList<ParticipantElement> = sortedList.map {
if (it.userId in selectedUserIds) {
it.selected = true
}
ParticipantElement(it, ParticipantElementType.PARTICIPANT.ordinal)
} as MutableList<ParticipantElement>
if (conversationToken.isNullOrEmpty() && searchQuery.isNullOrEmpty()) {
val newGroupElement = ParticipantElement(Pair(context.getString(R.string.nc_new_group), R.drawable.ic_people_group_white_24px), ParticipantElementType.PARTICIPANT_NEW_GROUP.ordinal)
participantElementsList.add(0, newGroupElement)
}
_contacts.postValue(sortedList)
_contacts.postValue(participantElementsList)
}
override suspend fun onError(errorModel: ErrorModel?) {

View File

@ -24,12 +24,13 @@ package com.nextcloud.talk.newarch.features.contactsflow.contacts
import androidx.lifecycle.LiveData
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.newarch.features.contactsflow.ParticipantElement
import com.otaliastudios.elements.Element
import com.otaliastudios.elements.Page
import com.otaliastudios.elements.Source
import com.otaliastudios.elements.extensions.MainSource
class ContactsViewSource<T : Participant>(private val data: LiveData<List<T>>, private val elementType: Int = 0, loadingIndicatorsEnabled: Boolean = true, errorIndicatorEnabled: Boolean = true, emptyIndicatorEnabled: Boolean = true) : MainSource<T>(loadingIndicatorsEnabled, errorIndicatorEnabled, emptyIndicatorEnabled) {
class ContactsViewSource<T : ParticipantElement>(private val data: LiveData<List<T>>, loadingIndicatorsEnabled: Boolean = true, errorIndicatorEnabled: Boolean = true, emptyIndicatorEnabled: Boolean = true) : MainSource<T>(loadingIndicatorsEnabled, errorIndicatorEnabled, emptyIndicatorEnabled) {
override fun onPageOpened(page: Page, dependencies: List<Element<*>>) {
super.onPageOpened(page, dependencies)
@ -39,7 +40,7 @@ class ContactsViewSource<T : Participant>(private val data: LiveData<List<T>>, p
}
override fun getElementType(data: T): Int {
return elementType
return data.elementType
}
override fun dependsOn(source: Source<*>) = false
@ -49,6 +50,14 @@ class ContactsViewSource<T : Participant>(private val data: LiveData<List<T>>, p
}
override fun areItemsTheSame(first: T, second: T): Boolean {
return first.userId == second.userId
if (first.elementType != second.elementType) {
return false
}
if (first.data is Participant && second.data is Participant) {
return first.data.userId == second.data.userId
}
return first.data == second.data
}
}