From 50385082423aa90f8e7a46a7902fc66a58fbfdf3 Mon Sep 17 00:00:00 2001 From: Taras <tarassmakula@gmail.com> Date: Tue, 1 Mar 2022 22:21:16 +0200 Subject: [PATCH] Create view types for invite list --- .../InviteMembersDialogFragment.kt | 12 +--- .../group_invite/InviteMembersViewModel.kt | 14 ++-- .../data_source/InviteMembersDataSource.kt | 30 ++++++-- .../list/InviteMemberViewHolder.kt | 70 +++++++++++++++++-- .../list/InviteMembersListAdapter.kt | 29 ++++++-- .../group_timeline/GroupTimelineFragment.kt | 2 +- .../futo/circles/mapping/MatrixUserMapping.kt | 4 +- .../circles/model/InviteMemberListItem.kt | 30 ++++++++ .../futo/circles/model/RoomMemberListItem.kt | 10 --- .../res/layout/group_timeline_fragment.xml | 4 +- .../res/layout/invite_header_list_item.xml | 9 +++ .../res/layout/invite_member_list_item.xml | 15 ---- .../layout/invite_members_dialog_fragment.xml | 1 - .../main/res/layout/no_results_list_item.xml | 12 ++++ app/src/main/res/layout/user_list_item.xml | 51 ++++++++++++++ app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/strings.xml | 3 + 17 files changed, 233 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/com/futo/circles/model/InviteMemberListItem.kt delete mode 100644 app/src/main/java/com/futo/circles/model/RoomMemberListItem.kt create mode 100644 app/src/main/res/layout/invite_header_list_item.xml delete mode 100644 app/src/main/res/layout/invite_member_list_item.xml create mode 100644 app/src/main/res/layout/no_results_list_item.xml create mode 100644 app/src/main/res/layout/user_list_item.xml diff --git a/app/src/main/java/com/futo/circles/feature/group_invite/InviteMembersDialogFragment.kt b/app/src/main/java/com/futo/circles/feature/group_invite/InviteMembersDialogFragment.kt index ecbb830d9..8812ae6f4 100644 --- a/app/src/main/java/com/futo/circles/feature/group_invite/InviteMembersDialogFragment.kt +++ b/app/src/main/java/com/futo/circles/feature/group_invite/InviteMembersDialogFragment.kt @@ -9,7 +9,6 @@ import com.futo.circles.databinding.InviteMembersDialogFragmentBinding import com.futo.circles.extensions.getQueryTextChangeStateFlow import com.futo.circles.extensions.observeData import com.futo.circles.feature.group_invite.list.InviteMembersListAdapter -import com.futo.circles.model.RoomMemberListItem import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.core.parameter.parametersOf @@ -20,7 +19,7 @@ class InviteMembersDialogFragment : private val args: InviteMembersDialogFragmentArgs by navArgs() private val viewModel by viewModel<InviteMembersViewModel> { parametersOf(args.roomId) } - private val listAdapter by lazy { InviteMembersListAdapter() } + private val listAdapter by lazy { InviteMembersListAdapter(viewModel::onUserSelected) } private val binding by lazy { getBinding() as InviteMembersDialogFragmentBinding @@ -45,13 +44,8 @@ class InviteMembersDialogFragment : binding.toolbar.title = it } - viewModel.usersLiveData.observeData(this) { users -> - setUserList(users) + viewModel.usersLiveData.observeData(this) { items -> + listAdapter.submitList(items) } } - - private fun setUserList(users: List<RoomMemberListItem>) { - listAdapter.submitList(users) - } - } \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/feature/group_invite/InviteMembersViewModel.kt b/app/src/main/java/com/futo/circles/feature/group_invite/InviteMembersViewModel.kt index 380096d8c..edc85c1f8 100644 --- a/app/src/main/java/com/futo/circles/feature/group_invite/InviteMembersViewModel.kt +++ b/app/src/main/java/com/futo/circles/feature/group_invite/InviteMembersViewModel.kt @@ -1,11 +1,11 @@ package com.futo.circles.feature.group_invite -import android.util.Log import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.futo.circles.extensions.launchUi import com.futo.circles.feature.group_invite.data_source.InviteMembersDataSource -import com.futo.circles.model.RoomMemberListItem +import com.futo.circles.model.InviteMemberListItem +import com.futo.circles.model.CirclesUser import kotlinx.coroutines.flow.* class InviteMembersViewModel( @@ -14,7 +14,7 @@ class InviteMembersViewModel( val titleLiveData = MutableLiveData(dataSource.getInviteTitle()) - val usersLiveData = MutableLiveData<List<RoomMemberListItem>>() + val usersLiveData = MutableLiveData<List<InviteMemberListItem>>() fun initSearchListener(queryFlow: StateFlow<String>) { launchUi { @@ -22,12 +22,12 @@ class InviteMembersViewModel( .debounce(500) .distinctUntilChanged() .flatMapLatest { query -> dataSource.search(query) } - .collectLatest { members -> - usersLiveData.postValue(members) - Log.d("MyLog", members.size.toString()) - } + .collectLatest { items -> usersLiveData.postValue(items) } } } + fun onUserSelected(member: CirclesUser){ + + } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/feature/group_invite/data_source/InviteMembersDataSource.kt b/app/src/main/java/com/futo/circles/feature/group_invite/data_source/InviteMembersDataSource.kt index 258a426ea..db6623d8b 100644 --- a/app/src/main/java/com/futo/circles/feature/group_invite/data_source/InviteMembersDataSource.kt +++ b/app/src/main/java/com/futo/circles/feature/group_invite/data_source/InviteMembersDataSource.kt @@ -4,7 +4,10 @@ import android.content.Context import androidx.lifecycle.asFlow import com.futo.circles.R import com.futo.circles.extensions.nameOrId -import com.futo.circles.mapping.toRoomMember +import com.futo.circles.mapping.toCirclesUser +import com.futo.circles.model.HeaderItem +import com.futo.circles.model.InviteMemberListItem +import com.futo.circles.model.NoResultsItem import com.futo.circles.provider.MatrixSessionProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.* @@ -27,7 +30,7 @@ class InviteMembersDataSource( suspend fun search(query: String) = combine(searchKnownUsers(query), searchSuggestions(query)) { knowUsers, suggestions -> - (knowUsers + suggestions).distinctBy { it.userId }.map { it.toRoomMember() } + buildList(knowUsers, suggestions) }.flowOn(Dispatchers.IO).distinctUntilChanged() @@ -46,9 +49,28 @@ class InviteMembersDataSource( emit(users ?: emptyList()) } + private fun buildList( + knowUsers: List<User>, + suggestions: List<User> + ): List<InviteMemberListItem> { + val list = mutableListOf<InviteMemberListItem>() + if (knowUsers.isNotEmpty()) { + list.add(HeaderItem.knownUsersHeader) + list.addAll(knowUsers.map { it.toCirclesUser() }) + } - private companion object { - private const val MAX_SUGGESTION_COUNT = 50 + val knowUsersIds = knowUsers.map { it.userId } + val filteredSuggestion = suggestions.filterNot { knowUsersIds.contains(it.userId) } + if (filteredSuggestion.isNotEmpty()) { + list.add(HeaderItem.suggestionHeader) + list.addAll(filteredSuggestion.map { it.toCirclesUser() }) + } + + if (list.isEmpty()) list.add(NoResultsItem()) + return list } + private companion object { + private const val MAX_SUGGESTION_COUNT = 25 + } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/feature/group_invite/list/InviteMemberViewHolder.kt b/app/src/main/java/com/futo/circles/feature/group_invite/list/InviteMemberViewHolder.kt index c2894e761..b06a8a015 100644 --- a/app/src/main/java/com/futo/circles/feature/group_invite/list/InviteMemberViewHolder.kt +++ b/app/src/main/java/com/futo/circles/feature/group_invite/list/InviteMemberViewHolder.kt @@ -1,22 +1,78 @@ package com.futo.circles.feature.group_invite.list +import android.util.Size +import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.futo.circles.base.ViewBindingHolder -import com.futo.circles.databinding.InviteMemberListItemBinding -import com.futo.circles.model.RoomMemberListItem +import com.futo.circles.base.context +import com.futo.circles.databinding.InviteHeaderListItemBinding +import com.futo.circles.databinding.NoResultsListItemBinding +import com.futo.circles.databinding.UserListItemBinding +import com.futo.circles.extensions.loadImage +import com.futo.circles.extensions.onClick +import com.futo.circles.model.HeaderItem +import com.futo.circles.model.InviteMemberListItem +import com.futo.circles.model.NoResultsItem +import com.futo.circles.model.CirclesUser -class InviteMemberViewHolder( +abstract class InviteMemberViewHolder(view: View) : RecyclerView.ViewHolder(view) { + abstract fun bind(data: InviteMemberListItem) +} + +class UserViewHolder( + parent: ViewGroup, + private val onMemberClicked: (Int) -> Unit +) : InviteMemberViewHolder(inflate(parent, UserListItemBinding::inflate)) { + + private companion object : ViewBindingHolder + + private val binding = baseBinding as UserListItemBinding + + init { + onClick(itemView) { position -> onMemberClicked(position) } + } + + override fun bind(data: InviteMemberListItem) { + if (data !is CirclesUser) return + + with(binding) { + ivUserImage.loadImage( + data.avatarUrl, + Size(ivUserImage.width, ivUserImage.height) + ) + tvUserName.text = data.name + tvUserId.text = data.id + } + } +} + +class HeaderViewHolder( parent: ViewGroup, -) : RecyclerView.ViewHolder(inflate(parent, InviteMemberListItemBinding::inflate)) { +) : InviteMemberViewHolder(inflate(parent, InviteHeaderListItemBinding::inflate)) { private companion object : ViewBindingHolder - private val binding = baseBinding as InviteMemberListItemBinding + private val binding = baseBinding as InviteHeaderListItemBinding + override fun bind(data: InviteMemberListItem) { + if (data !is HeaderItem) return - fun bind(data: RoomMemberListItem) { - binding.tvText.text = data.name + " / " + data.id + binding.tvHeader.text = context.getString(data.titleRes) } +} +class NoResultViewHolder( + parent: ViewGroup, +) : InviteMemberViewHolder(inflate(parent, NoResultsListItemBinding::inflate)) { + + private companion object : ViewBindingHolder + + private val binding = baseBinding as NoResultsListItemBinding + + override fun bind(data: InviteMemberListItem) { + if (data !is NoResultsItem) return + + binding.tvMessage.text = context.getString(data.titleRes) + } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/feature/group_invite/list/InviteMembersListAdapter.kt b/app/src/main/java/com/futo/circles/feature/group_invite/list/InviteMembersListAdapter.kt index de81e0ebc..bc396a40b 100644 --- a/app/src/main/java/com/futo/circles/feature/group_invite/list/InviteMembersListAdapter.kt +++ b/app/src/main/java/com/futo/circles/feature/group_invite/list/InviteMembersListAdapter.kt @@ -2,17 +2,34 @@ package com.futo.circles.feature.group_invite.list import android.view.ViewGroup import com.futo.circles.base.BaseRvAdapter -import com.futo.circles.model.RoomMemberListItem +import com.futo.circles.model.HeaderItem +import com.futo.circles.model.InviteMemberListItem +import com.futo.circles.model.NoResultsItem +import com.futo.circles.model.CirclesUser -class InviteMembersListAdapter() : BaseRvAdapter<RoomMemberListItem, InviteMemberViewHolder>( +private enum class InviteListViewType { Header, User, NoResults } + +class InviteMembersListAdapter( + private val onUserSelected: (CirclesUser) -> Unit +) : BaseRvAdapter<InviteMemberListItem, InviteMemberViewHolder>( DefaultIdEntityCallback() ) { + override fun getItemViewType(position: Int): Int = when (getItem(position)) { + is HeaderItem -> InviteListViewType.Header.ordinal + is CirclesUser -> InviteListViewType.User.ordinal + is NoResultsItem -> InviteListViewType.NoResults.ordinal + } - override fun onCreateViewHolder( - parent: ViewGroup, - viewType: Int - ): InviteMemberViewHolder = InviteMemberViewHolder(parent = parent) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): InviteMemberViewHolder { + return when (InviteListViewType.values()[viewType]) { + InviteListViewType.Header -> HeaderViewHolder(parent) + InviteListViewType.User -> UserViewHolder( + parent, + onMemberClicked = { position -> onUserSelected(getItem(position) as CirclesUser) }) + InviteListViewType.NoResults -> NoResultViewHolder(parent) + } + } override fun onBindViewHolder(holder: InviteMemberViewHolder, position: Int) { holder.bind(getItem(position)) diff --git a/app/src/main/java/com/futo/circles/feature/group_timeline/GroupTimelineFragment.kt b/app/src/main/java/com/futo/circles/feature/group_timeline/GroupTimelineFragment.kt index a4c3765a0..561ca3f5c 100644 --- a/app/src/main/java/com/futo/circles/feature/group_timeline/GroupTimelineFragment.kt +++ b/app/src/main/java/com/futo/circles/feature/group_timeline/GroupTimelineFragment.kt @@ -40,7 +40,7 @@ class GroupTimelineFragment : Fragment(R.layout.group_timeline_fragment), GroupP super.onViewCreated(view, savedInstanceState) setHasOptionsMenu(true) - binding.tvGroupTimeline.apply { + binding.rvGroupTimeline.apply { adapter = listAdapter addItemDecoration( BaseRvDecoration.OffsetDecoration<GroupPostViewHolder>( diff --git a/app/src/main/java/com/futo/circles/mapping/MatrixUserMapping.kt b/app/src/main/java/com/futo/circles/mapping/MatrixUserMapping.kt index a837f7780..e66dcd10b 100644 --- a/app/src/main/java/com/futo/circles/mapping/MatrixUserMapping.kt +++ b/app/src/main/java/com/futo/circles/mapping/MatrixUserMapping.kt @@ -1,9 +1,9 @@ package com.futo.circles.mapping -import com.futo.circles.model.RoomMemberListItem +import com.futo.circles.model.CirclesUser import org.matrix.android.sdk.api.session.user.model.User -fun User.toRoomMember() = RoomMemberListItem( +fun User.toCirclesUser() = CirclesUser( id = userId, name = displayName ?: userId, avatarUrl = avatarUrl ?: "" diff --git a/app/src/main/java/com/futo/circles/model/InviteMemberListItem.kt b/app/src/main/java/com/futo/circles/model/InviteMemberListItem.kt new file mode 100644 index 000000000..ead4815d4 --- /dev/null +++ b/app/src/main/java/com/futo/circles/model/InviteMemberListItem.kt @@ -0,0 +1,30 @@ +package com.futo.circles.model + +import com.futo.circles.R +import com.futo.circles.base.IdEntity + +sealed class InviteMemberListItem : IdEntity<String> + +data class HeaderItem( + val titleRes: Int +) : InviteMemberListItem() { + override val id: String = titleRes.toString() + + companion object { + val knownUsersHeader = HeaderItem(R.string.known_users) + val suggestionHeader = HeaderItem(R.string.suggestion) + } +} + +data class CirclesUser( + override val id: String, + val name: String, + val avatarUrl: String, + val isSelected: Boolean = false +) : InviteMemberListItem() + +data class NoResultsItem( + val titleRes: Int = R.string.no_results +) : InviteMemberListItem() { + override val id: String = titleRes.toString() +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/circles/model/RoomMemberListItem.kt b/app/src/main/java/com/futo/circles/model/RoomMemberListItem.kt deleted file mode 100644 index 4b236be0c..000000000 --- a/app/src/main/java/com/futo/circles/model/RoomMemberListItem.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.futo.circles.model - -import com.futo.circles.base.IdEntity - -data class RoomMemberListItem( - override val id: String, - val name: String, - val avatarUrl: String, - val isSelected: Boolean = false -) : IdEntity<String> \ No newline at end of file diff --git a/app/src/main/res/layout/group_timeline_fragment.xml b/app/src/main/res/layout/group_timeline_fragment.xml index 5873afd03..30e82a65a 100644 --- a/app/src/main/res/layout/group_timeline_fragment.xml +++ b/app/src/main/res/layout/group_timeline_fragment.xml @@ -6,7 +6,7 @@ android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView - android:id="@+id/tvGroupTimeline" + android:id="@+id/rvGroupTimeline" android:layout_width="match_parent" android:layout_height="match_parent" android:clipToPadding="false" @@ -21,7 +21,7 @@ android:layout_gravity="bottom|end" android:layout_margin="16dp" app:fabSize="normal" - app:layout_anchor="@id/tvGroupTimeline" + app:layout_anchor="@id/rvGroupTimeline" app:srcCompat="@drawable/ic_create" app:tint="@color/white" tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/invite_header_list_item.xml b/app/src/main/res/layout/invite_header_list_item.xml new file mode 100644 index 000000000..ab9cc4dd2 --- /dev/null +++ b/app/src/main/res/layout/invite_header_list_item.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/tvHeader" + style="@style/headline" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="8dp" + tools:text="Header" /> \ No newline at end of file diff --git a/app/src/main/res/layout/invite_member_list_item.xml b/app/src/main/res/layout/invite_member_list_item.xml deleted file mode 100644 index 132aefc28..000000000 --- a/app/src/main/res/layout/invite_member_list_item.xml +++ /dev/null @@ -1,15 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <TextView - android:id="@+id/tvText" - android:layout_width="0dp" - android:layout_height="wrap_content" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - -</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/layout/invite_members_dialog_fragment.xml b/app/src/main/res/layout/invite_members_dialog_fragment.xml index cb3fc5824..fbd33cb60 100644 --- a/app/src/main/res/layout/invite_members_dialog_fragment.xml +++ b/app/src/main/res/layout/invite_members_dialog_fragment.xml @@ -41,7 +41,6 @@ android:id="@+id/rvUsers" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginTop="8dp" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" diff --git a/app/src/main/res/layout/no_results_list_item.xml b/app/src/main/res/layout/no_results_list_item.xml new file mode 100644 index 000000000..fcff59970 --- /dev/null +++ b/app/src/main/res/layout/no_results_list_item.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/tvMessage" + style="@style/headline" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingVertical="48dp" + tools:text="@string/no_results"> + +</TextView> \ No newline at end of file diff --git a/app/src/main/res/layout/user_list_item.xml b/app/src/main/res/layout/user_list_item.xml new file mode 100644 index 000000000..4ce0814ac --- /dev/null +++ b/app/src/main/res/layout/user_list_item.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?selectableItemBackground" + android:clickable="true" + android:focusable="true" + android:paddingHorizontal="8dp" + android:paddingVertical="4dp"> + + <com.google.android.material.imageview.ShapeableImageView + android:id="@+id/ivUserImage" + android:layout_width="40dp" + android:layout_height="0dp" + app:layout_constraintDimensionRatio="w,1:1" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.App.CornerSize50Percent" + tools:background="@color/blue" /> + + <TextView + android:id="@+id/tvUserName" + style="@style/body" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="8dp" + android:ellipsize="end" + android:lines="1" + app:layout_constraintBottom_toTopOf="@id/tvUserId" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/ivUserImage" + app:layout_constraintTop_toTopOf="@id/ivUserImage" + app:layout_constraintVertical_chainStyle="packed" + tools:text="Android01" /> + + <TextView + android:id="@+id/tvUserId" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:ellipsize="end" + android:lines="1" + android:textSize="13sp" + app:layout_constraintBottom_toBottomOf="@id/ivUserImage" + app:layout_constraintEnd_toEndOf="@id/tvUserName" + app:layout_constraintStart_toStartOf="@id/tvUserName" + app:layout_constraintTop_toBottomOf="@id/tvUserName" + tools:text="Android01@domain" /> + +</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index fa720bdd1..83809b81d 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -4,4 +4,5 @@ <dimen name="group_post_item_offset">4dp</dimen> <dimen name="reply_post_item_margin">24dp</dimen> <dimen name="post_text_side_margin">24dp</dimen> + <dimen name="invite_list_side_margin">8dp</dimen> </resources> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 15530f2eb..185312242 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -29,6 +29,9 @@ <string name="invite_members">Invite members</string> <string name="invite_members_to_format">Invite members to %s</string> <string name="search_by_name_or_id">Search by name or id</string> + <string name="known_users">Known users</string> + <string name="suggestion">Suggestions</string> + <string name="no_results">No results</string> <plurals name="member_plurals"> <item quantity="one">%d member</item> -- GitLab