Update UI for contacts

Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
Mario Danic 2020-01-24 13:56:16 +01:00
parent 3c15393ba4
commit c80b611ae9
No known key found for this signature in database
GPG Key ID: CDE0BBD2738C4CC0
12 changed files with 125 additions and 40 deletions

View File

@ -92,8 +92,8 @@ class NextcloudTalkRepositoryImpl(private val apiService: ApiService) : Nextclou
}
}
override suspend fun getContactsForUser(user: UserNgEntity, searchQuery: String?, conversationToken: String?): List<Participant> {
return apiService.getContacts(authorization = user.getCredentials(), url = ApiUtils.getUrlForContactsSearch(user.baseUrl), shareTypes = ApiUtils.getShareTypesForContactsSearch(), options = ApiUtils.getQueryMapForContactsSearch(searchQuery, conversationToken)).ocs.data.map {
override suspend fun getContactsForUser(user: UserNgEntity, groupConversation: Boolean, searchQuery: String?, conversationToken: String?): List<Participant> {
return apiService.getContacts(authorization = user.getCredentials(), url = ApiUtils.getUrlForContactsSearch(user.baseUrl), shareTypes = ApiUtils.getShareTypesForContactsSearch(groupConversation), options = ApiUtils.getQueryMapForContactsSearch(searchQuery, conversationToken)).ocs.data.map {
val participant = Participant()
participant.userId = it.id
participant.displayName = it.label

View File

@ -33,7 +33,7 @@ import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
import com.nextcloud.talk.newarch.local.models.UserNgEntity
interface NextcloudTalkRepository {
suspend fun getContactsForUser(user: UserNgEntity, searchQuery: String?, conversationToken: String?): List<Participant>
suspend fun getContactsForUser(user: UserNgEntity, groupConversation: Boolean, searchQuery: String?, conversationToken: String?): List<Participant>
suspend fun registerPushWithServerForUser(user: UserNgEntity, options: Map<String, String>): PushRegistrationOverall
suspend fun unregisterPushWithServerForUser(user: UserNgEntity): GenericOverall
suspend fun registerPushWithProxyForUser(user: UserNgEntity, options: Map<String, String>): Any

View File

@ -34,6 +34,6 @@ class GetContactsUseCase constructor(
) : UseCase<List<Participant>, Any?>(apiErrorHandler) {
override suspend fun run(params: Any?): List<Participant> {
val definitionParameters = params as DefinitionParameters
return nextcloudTalkRepository.getContactsForUser(definitionParameters[0], definitionParameters[1], definitionParameters[2])
return nextcloudTalkRepository.getContactsForUser(definitionParameters[0], definitionParameters[1], definitionParameters[2], definitionParameters[3])
}
}

View File

@ -51,7 +51,7 @@ open class ContactPresenter<T : Any>(context: Context, onElementClick: ((Page, H
private val globalService: GlobalService by inject()
override val elementTypes: Collection<Int>
get() = listOf(ParticipantElementType.PARTICIPANT.ordinal, ParticipantElementType.PARTICIPANT_SELECTED.ordinal, ParticipantElementType.PARTICIPANT_HEADER.ordinal, ParticipantElementType.PARTICIPANT_FOOTER.ordinal)
get() = listOf(ParticipantElementType.PARTICIPANT.ordinal, ParticipantElementType.PARTICIPANT_SELECTED.ordinal, ParticipantElementType.PARTICIPANT_HEADER.ordinal, ParticipantElementType.PARTICIPANT_FOOTER.ordinal, ParticipantElementType.PARTICIPANT_NEW_GROUP.ordinal, ParticipantElementType.PARTICIPANT_JOIN_VIA_LINK.ordinal)
override fun onCreate(parent: ViewGroup, elementType: Int): Holder {
return when (elementType) {
@ -64,9 +64,13 @@ open class ContactPresenter<T : Any>(context: Context, onElementClick: ((Page, H
ParticipantElementType.PARTICIPANT_HEADER.ordinal -> {
Holder(getLayoutInflater().inflate(R.layout.rv_item_title_header, parent, false))
}
else -> {
ParticipantElementType.PARTICIPANT_FOOTER.ordinal -> {
Holder(getLayoutInflater().inflate(R.layout.rv_item_participant_rv_footer, parent, false))
}
else -> {
// for join via link and new group
Holder(getLayoutInflater().inflate(R.layout.rv_item_contact, parent, false))
}
}
}
@ -120,8 +124,16 @@ open class ContactPresenter<T : Any>(context: Context, onElementClick: ((Page, H
}
} else if (element.type == ParticipantElementType.PARTICIPANT_HEADER.ordinal) {
holder.itemView.titleTextView.text = (element.data as HeaderSource.Data<*, *>).header.toString()
} else {
} 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<*, *>
holder.itemView.participantNameTextView.text = pairData.first as CharSequence
holder.itemView.avatarImageView.load(Images().getImageWithBackground(context, pairData.second as Int))
} else {
val pairData = element.data as Pair<*, *>
holder.itemView.participantNameTextView.text = pairData.first as CharSequence
holder.itemView.avatarImageView.load(Images().getImageWithBackground(context, pairData.second as Int))
}
}
}

View File

@ -26,5 +26,7 @@ enum class ParticipantElementType {
PARTICIPANT,
PARTICIPANT_SELECTED,
PARTICIPANT_HEADER,
PARTICIPANT_FOOTER
PARTICIPANT_FOOTER,
PARTICIPANT_NEW_GROUP,
PARTICIPANT_JOIN_VIA_LINK
}

View File

@ -34,14 +34,12 @@ import androidx.recyclerview.widget.RecyclerView
import com.bluelinelabs.conductor.autodispose.ControllerScopeProvider
import com.nextcloud.talk.R
import com.nextcloud.talk.models.json.participants.Participant
import com.nextcloud.talk.newarch.features.contactsflow.source.FixedListSource
import com.nextcloud.talk.newarch.mvvm.BaseView
import com.nextcloud.talk.newarch.mvvm.ext.initRecyclerView
import com.nextcloud.talk.newarch.utils.ElementPayload
import com.nextcloud.talk.utils.bundle.BundleKeys
import com.otaliastudios.elements.Adapter
import com.otaliastudios.elements.Element
import com.otaliastudios.elements.Page
import com.otaliastudios.elements.Presenter
import com.otaliastudios.elements.*
import com.uber.autodispose.lifecycle.LifecycleScopeProvider
import kotlinx.android.synthetic.main.contacts_list_view.view.*
import kotlinx.android.synthetic.main.conversations_list_view.view.recyclerView
@ -59,6 +57,9 @@ class ContactsView<T : Any>(private val bundle: Bundle? = null) : BaseView() {
return R.layout.contacts_list_view
}
private val isGroupConversation = bundle?.containsKey(BundleKeys.KEY_CONVERSATION_NAME) == true
private val hasToken = bundle?.containsKey(BundleKeys.KEY_CONVERSATION_TOKEN) == true
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup
@ -71,9 +72,11 @@ class ContactsView<T : Any>(private val bundle: Bundle? = null) : BaseView() {
// todo - change empty state magic
participantsAdapter = Adapter.builder(this)
.addSource(FixedListSource(listOf(Pair(context.getString(R.string.nc_new_group), R.drawable.ic_people_group_white_24px)), ParticipantElementType.PARTICIPANT_NEW_GROUP.ordinal))
//.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(ContactsHeaderSource(activity as Context, ParticipantElementType.PARTICIPANT_HEADER.ordinal))
.addSource(ContactsFooterSource(activity as Context, ParticipantElementType.PARTICIPANT_FOOTER.ordinal))
.addSource(ContactsViewFooterSource(activity as Context, ParticipantElementType.PARTICIPANT_FOOTER.ordinal))
.addPresenter(ContactPresenter(activity as Context, ::onElementClick))
.addPresenter(Presenter.forLoadingIndicator(activity as Context, R.layout.loading_state))
.addPresenter(Presenter.forEmptyIndicator(activity as Context, R.layout.message_state))
@ -136,7 +139,7 @@ class ContactsView<T : Any>(private val bundle: Bundle? = null) : BaseView() {
}
viewModel.initialize(bundle?.getString(BundleKeys.KEY_CONVERSATION_TOKEN))
viewModel.initialize(bundle?.getString(BundleKeys.KEY_CONVERSATION_TOKEN), bundle?.containsKey(BundleKeys.KEY_CONVERSATION_NAME) == true)
return view
}
@ -149,26 +152,46 @@ class ContactsView<T : Any>(private val bundle: Bundle? = null) : BaseView() {
private fun onElementClick(page: Page, holder: Presenter.Holder, element: Element<T>) {
if (element.data is Participant?) {
val participant = element.data as Participant?
val isElementSelected = participant?.selected == true
participant?.let {
if (isElementSelected) {
viewModel.unselectParticipant(it)
} else {
viewModel.selectParticipant(it)
}
it.selected = !isElementSelected
if (element.type == ParticipantElementType.PARTICIPANT_SELECTED.ordinal) {
participantsAdapter.notifyItemRangeChanged(0, participantsAdapter.itemCount, ElementPayload.SELECTION_TOGGLE)
} else {
participantsAdapter.notifyItemChanged(holder.adapterPosition, ElementPayload.SELECTION_TOGGLE)
}
if (isGroupConversation || hasToken) {
val isElementSelected = participant?.selected == true
participant?.let {
if (isElementSelected) {
viewModel.unselectParticipant(it)
} else {
viewModel.selectParticipant(it)
}
it.selected = !isElementSelected
if (element.type == ParticipantElementType.PARTICIPANT_SELECTED.ordinal) {
participantsAdapter.notifyItemRangeChanged(0, participantsAdapter.itemCount, ElementPayload.SELECTION_TOGGLE)
} else {
participantsAdapter.notifyItemChanged(holder.adapterPosition, ElementPayload.SELECTION_TOGGLE)
}
}
} else {
participant?.let {
// create room etc etc
}
}
} else if (element.type == ParticipantElementType.PARTICIPANT_NEW_GROUP.ordinal) {
} else if (element.type == ParticipantElementType.PARTICIPANT_JOIN_VIA_LINK.ordinal) {
}
}
override fun getTitle(): String? {
return resources?.getString(R.string.nc_select_contacts)
return when {
isGroupConversation -> {
resources?.getString(R.string.nc_select_contacts)
}
hasToken -> {
resources?.getString(R.string.nc_select_new_contacts)
}
else -> {
resources?.getString(R.string.nc_select_contact)
}
}
}
}

View File

@ -29,7 +29,7 @@ import com.otaliastudios.elements.Page
import com.otaliastudios.elements.Source
import com.otaliastudios.elements.extensions.FooterSource
class ContactsFooterSource(private val context: Context, private val elementType: Int) : FooterSource<Participant, String>() {
class ContactsViewFooterSource(private val context: Context, private val elementType: Int) : FooterSource<Participant, String>() {
private var lastAnchor: Participant? = null
override fun dependsOn(source: Source<*>): Boolean {

View File

@ -45,11 +45,13 @@ class ContactsViewModel constructor(
private var searchQuery: String? = null
private var conversationToken: String? = null
private var groupConversation: Boolean = false
private var initialized = false
fun initialize(conversationToken: String?) {
if (!initialized || conversationToken != this.conversationToken) {
fun initialize(conversationToken: String?, groupConversation: Boolean) {
if (!initialized || conversationToken != this.conversationToken || groupConversation != this.groupConversation) {
this.conversationToken = conversationToken
this.groupConversation = groupConversation
loadContacts()
}
}
@ -70,7 +72,7 @@ class ContactsViewModel constructor(
}
fun loadContacts() {
getContactsUseCase.invoke(viewModelScope, parametersOf(globalService.currentUserLiveData.value, searchQuery, conversationToken), object :
getContactsUseCase.invoke(viewModelScope, parametersOf(globalService.currentUserLiveData.value, groupConversation, searchQuery, conversationToken), object :
UseCaseResponse<List<Participant>> {
override suspend fun onSuccess(result: List<Participant>) {
val sortPriority = mapOf("users" to 0, "groups" to 1, "emails" to 2, "circles" to 0)

View File

@ -0,0 +1,41 @@
/*
*
* * Nextcloud Talk application
* *
* * @author Mario Danic
* * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU General Public License as published by
* * the Free Software Foundation, either version 3 of the License, or
* * at your option) any later version.
* *
* * This program is distributed in the hope that it will be useful,
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* * GNU General Public License for more details.
* *
* * You should have received a copy of the GNU General Public License
* * along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.nextcloud.talk.newarch.features.contactsflow.source
import com.nextcloud.talk.newarch.features.contactsflow.ContactsViewSource
import com.otaliastudios.elements.Source
import com.otaliastudios.elements.extensions.ListSource
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
}
}

View File

@ -67,16 +67,18 @@ public class ApiUtils {
return baseUrl + ocsApiVersion + "/core/autocomplete/get";
}
public static List<String> getShareTypesForContactsSearch() {
public static List<String> getShareTypesForContactsSearch(boolean groupConversation) {
List<String> shareTypesList = new ArrayList<>();
// user
shareTypesList.add("0");
// group
shareTypesList.add("1");
// group
shareTypesList.add("4");
// remote/circles
shareTypesList.add("7");
if (groupConversation) {
// group
shareTypesList.add("1");
// email
shareTypesList.add("4");
// remote/circles
shareTypesList.add("7");
}
return shareTypesList;
}

View File

@ -27,7 +27,7 @@
android:icon="@drawable/ic_search_white_24dp"
android:title="@string/nc_search"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="collapseActionView|always" />
app:showAsAction="collapseActionView|ifRoom" />
<item
android:id="@+id/contacts_selection_done"

View File

@ -180,7 +180,9 @@
conversations list</string>
<!-- Contacts -->
<string name="nc_select_contact">Find participant</string>
<string name="nc_select_contacts">Find participants</string>
<string name="nc_select_new_contacts">Find new participants</string>
<string name="nc_contacts_done">Done</string>
<!-- Permissions -->
@ -339,4 +341,5 @@
<string name="path_password_strike_through" translatable="false"
tools:override="true">M3.27,4.27L19.74,20.74</string>
<string name="nc_search_for_more">Search for more participants</string>
<string name="nc_new_group">New group</string>
</resources>