show detailed list of voters

+ refactoring adapter and viewholders for result screen

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2022-06-29 15:17:50 +02:00 committed by Andy Scherzinger (Rebase PR Action)
parent 909ee07ce6
commit 73d48a395c
13 changed files with 324 additions and 179 deletions

View File

@ -0,0 +1,19 @@
package com.nextcloud.talk.polls.adapters
import com.nextcloud.talk.R
data class PollResultHeaderItem(
val name: String,
val percent: Int,
val selfVoted: Boolean
) : PollResultItem {
override fun getViewType(): Int {
return VIEW_TYPE
}
companion object {
// layout is used as view type for uniqueness
public val VIEW_TYPE: Int = R.layout.poll_result_header_item
}
}

View File

@ -0,0 +1,29 @@
package com.nextcloud.talk.polls.adapters
import android.annotation.SuppressLint
import android.graphics.Typeface
import com.nextcloud.talk.databinding.PollResultHeaderItemBinding
import com.nextcloud.talk.models.database.UserEntity
class PollResultHeaderViewHolder(
private val user: UserEntity,
override val binding: PollResultHeaderItemBinding
) : PollResultViewHolder(binding) {
@SuppressLint("SetTextI18n")
override fun bind(pollResultItem: PollResultItem, clickListener: PollResultItemClickListener) {
val item = pollResultItem as PollResultHeaderItem
binding.root.setOnClickListener { clickListener.onClick(pollResultItem) }
binding.pollOptionText.text = item.name
binding.pollOptionPercentText.text = "${item.percent}%"
if (item.selfVoted) {
binding.pollOptionText.setTypeface(null, Typeface.BOLD)
binding.pollOptionPercentText.setTypeface(null, Typeface.BOLD)
}
binding.pollOptionBar.progress = item.percent
}
}

View File

@ -1,10 +1,7 @@
package com.nextcloud.talk.polls.adapters
import com.nextcloud.talk.polls.model.PollDetails
interface PollResultItem {
class PollResultItem(
val name: String,
val percent: Int,
val selfVoted: Boolean,
val voters: List<PollDetails>?
)
fun getViewType(): Int
// fun getView(inflater: LayoutInflater?, convertView: View?): View?
}

View File

@ -1,5 +1,5 @@
package com.nextcloud.talk.polls.adapters
interface PollResultItemClickListener {
fun onClick(pollResultItem: PollResultItem)
fun onClick(pollResultHeaderItem: PollResultHeaderItem)
}

View File

@ -1,107 +1,10 @@
package com.nextcloud.talk.polls.adapters
import android.annotation.SuppressLint
import android.graphics.Typeface
import android.text.TextUtils
import android.view.View
import android.widget.LinearLayout
import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.generic.RoundingParams
import com.facebook.drawee.interfaces.DraweeController
import com.facebook.drawee.view.SimpleDraweeView
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.databinding.PollResultItemBinding
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.polls.model.PollDetails
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
import androidx.viewbinding.ViewBinding
class PollResultViewHolder(
private val user: UserEntity,
private val binding: PollResultItemBinding
abstract class PollResultViewHolder(
open val binding: ViewBinding
) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(pollResultItem: PollResultItem, clickListener: PollResultItemClickListener) {
binding.root.setOnClickListener { clickListener.onClick(pollResultItem) }
binding.root.setOnClickListener { clickListener.onClick(pollResultItem) }
binding.pollOptionText.text = pollResultItem.name
binding.pollOptionPercentText.text = "${pollResultItem.percent}%"
if (pollResultItem.selfVoted) {
binding.pollOptionText.setTypeface(null, Typeface.BOLD)
binding.pollOptionPercentText.setTypeface(null, Typeface.BOLD)
}
binding.pollOptionBar.progress = pollResultItem.percent
if (!pollResultItem.voters.isNullOrEmpty()) {
binding.pollOptionDetail.visibility = View.VISIBLE
val lp = LinearLayout.LayoutParams(
60,
50
)
pollResultItem.voters.forEach {
val avatar = SimpleDraweeView(binding.root.context)
avatar.layoutParams = lp
val roundingParams = RoundingParams.fromCornersRadius(5f)
roundingParams.roundAsCircle = true
avatar.hierarchy.roundingParams = roundingParams
avatar.controller = getAvatarDraweeController(it)
binding.pollOptionDetail.addView(avatar)
}
} else {
binding.pollOptionDetail.visibility = View.GONE
}
}
private fun getAvatarDraweeController(pollDetail: PollDetails): DraweeController? {
if (pollDetail.actorType == "guests") {
var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest)
if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) {
displayName = pollDetail.actorDisplayName!!
}
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
// .setOldController(binding.avatar.controller)
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForGuestAvatar(
user.baseUrl,
displayName,
false
),
null
)
)
.build()
return draweeController
} else if (pollDetail.actorType == "users") {
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
// .setOldController(binding.avatar.controller)
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
user.baseUrl,
pollDetail.actorId,
false
),
null
)
)
.build()
return draweeController
}
return null
}
abstract fun bind(pollResultItem: PollResultItem, clickListener: PollResultItemClickListener)
}

View File

@ -0,0 +1,18 @@
package com.nextcloud.talk.polls.adapters
import com.nextcloud.talk.R
import com.nextcloud.talk.polls.model.PollDetails
data class PollResultVoterItem(
val details: PollDetails
) : PollResultItem {
override fun getViewType(): Int {
return VIEW_TYPE
}
companion object {
// layout is used as view type for uniqueness
public val VIEW_TYPE: Int = R.layout.poll_result_voter_item
}
}

View File

@ -0,0 +1,84 @@
package com.nextcloud.talk.polls.adapters
import android.annotation.SuppressLint
import android.text.TextUtils
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.interfaces.DraweeController
import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.databinding.PollResultVoterItemBinding
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.polls.model.PollDetails
import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils
class PollResultVoterViewHolder(
private val user: UserEntity,
override val binding: PollResultVoterItemBinding
) : PollResultViewHolder(binding) {
@SuppressLint("SetTextI18n")
override fun bind(pollResultItem: PollResultItem, clickListener: PollResultItemClickListener) {
val item = pollResultItem as PollResultVoterItem
// binding.root.setOnClickListener { clickListener.onClick(pollResultVoterItem) }
// binding.pollVoterAvatar = pollResultHeaderItem.name
binding.pollVoterName.text = item.details.actorDisplayName
// val lp = LinearLayout.LayoutParams(
// 60,
// 50
// )
//
// val avatar = SimpleDraweeView(binding.root.context)
// avatar.layoutParams = lp
// val roundingParams = RoundingParams.fromCornersRadius(5f)
// roundingParams.roundAsCircle = true
//
// binding.pollVoterAvatar.hierarchy.roundingParams = roundingParams
binding.pollVoterAvatar.controller = getAvatarDraweeController(item.details)
}
private fun getAvatarDraweeController(pollDetail: PollDetails): DraweeController? {
if (pollDetail.actorType == "guests") {
var displayName = NextcloudTalkApplication.sharedApplication?.resources?.getString(R.string.nc_guest)
if (!TextUtils.isEmpty(pollDetail.actorDisplayName)) {
displayName = pollDetail.actorDisplayName!!
}
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
// .setOldController(binding.avatar.controller)
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForGuestAvatar(
user.baseUrl,
displayName,
false
),
null
)
)
.build()
return draweeController
} else if (pollDetail.actorType == "users") {
val draweeController: DraweeController = Fresco.newDraweeControllerBuilder()
// .setOldController(binding.avatar.controller)
.setAutoPlayAnimations(true)
.setImageRequest(
DisplayUtils.getImageRequestForUrl(
ApiUtils.getUrlForAvatar(
user.baseUrl,
pollDetail.actorId,
false
),
null
)
)
.build()
return draweeController
}
return null
}
}

View File

@ -3,26 +3,56 @@ package com.nextcloud.talk.polls.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.talk.databinding.PollResultItemBinding
import com.nextcloud.talk.databinding.PollResultHeaderItemBinding
import com.nextcloud.talk.databinding.PollResultVoterItemBinding
import com.nextcloud.talk.models.database.UserEntity
class PollResultsAdapter(
private val user: UserEntity,
private val clickListener: PollResultItemClickListener,
) : RecyclerView.Adapter<PollResultViewHolder>() {
internal var list: MutableList<PollResultItem> = ArrayList<PollResultItem>()
internal var list: MutableList<PollResultItem> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PollResultViewHolder {
val itemBinding = PollResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PollResultViewHolder(user, itemBinding)
when (viewType) {
PollResultHeaderItem.VIEW_TYPE -> {
val itemBinding = PollResultHeaderItemBinding.inflate(
LayoutInflater.from(parent.context), parent,
false
)
return PollResultHeaderViewHolder(user, itemBinding)
}
PollResultVoterItem.VIEW_TYPE -> {
val itemBinding = PollResultVoterItemBinding.inflate(
LayoutInflater.from(parent.context), parent,
false
)
return PollResultVoterViewHolder(user, itemBinding)
}
}
val itemBinding = PollResultHeaderItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return PollResultHeaderViewHolder(user, itemBinding)
}
override fun onBindViewHolder(holder: PollResultViewHolder, position: Int) {
val pollResultItem = list[position]
holder.bind(pollResultItem, clickListener)
when (holder.itemViewType) {
PollResultHeaderItem.VIEW_TYPE -> {
val pollResultItem = list[position]
holder.bind(pollResultItem as PollResultHeaderItem, clickListener)
}
PollResultVoterItem.VIEW_TYPE -> {
val pollResultItem = list[position]
holder.bind(pollResultItem as PollResultVoterItem, clickListener)
}
}
}
override fun getItemCount(): Int {
return list.size
}
override fun getItemViewType(position: Int): Int {
return list[position].getViewType()
}
}

View File

@ -22,7 +22,6 @@
package com.nextcloud.talk.polls.ui
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -33,10 +32,9 @@ import autodagger.AutoInjector
import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.databinding.DialogPollResultsBinding
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.polls.adapters.PollResultItem
import com.nextcloud.talk.polls.adapters.PollResultHeaderItem
import com.nextcloud.talk.polls.adapters.PollResultItemClickListener
import com.nextcloud.talk.polls.adapters.PollResultsAdapter
import com.nextcloud.talk.polls.model.Poll
import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
import com.nextcloud.talk.polls.viewmodels.PollResultsViewModel
import javax.inject.Inject
@ -81,11 +79,18 @@ class PollResultsFragment(
parentViewModel.viewState.observe(viewLifecycleOwner) { state ->
if (state is PollMainViewModel.PollResultState) {
initAdapter()
initPollResults(state.poll)
viewModel.setPoll(state.poll)
initEditButton(state.showEditButton)
initCloseButton(state.showCloseButton)
}
}
viewModel.items.observe(viewLifecycleOwner) {
val adapter = PollResultsAdapter(user, this).apply {
list = it
}
binding.pollResultsList.adapter = adapter
}
}
private fun initAdapter() {
@ -94,51 +99,6 @@ class PollResultsFragment(
_binding?.pollResultsList?.layoutManager = LinearLayoutManager(context)
}
private fun initPollResults(poll: Poll) {
if (poll.details != null) {
val votersAmount = poll.details.size
val oneVoteInPercent = 100 / votersAmount // TODO: poll.numVoters when fixed on api
poll.options?.forEachIndexed { index, option ->
val votersForThisOption = poll.details.filter { it.optionId == index }
val optionsPercent = oneVoteInPercent * votersForThisOption.size
val pollResultItem = PollResultItem(
option,
optionsPercent,
isOptionSelfVoted(poll, index),
votersForThisOption
)
adapter?.list?.add(pollResultItem)
}
} else if (poll.votes != null) {
val votersAmount = poll.numVoters
val oneVoteInPercent = 100 / votersAmount
poll.options?.forEachIndexed { index, option ->
var votersAmountForThisOption = poll.votes.filter { it.key.toInt() == index }[index.toString()]
if (votersAmountForThisOption == null) {
votersAmountForThisOption = 0
}
val optionsPercent = oneVoteInPercent * votersAmountForThisOption
val pollResultItem = PollResultItem(
option,
optionsPercent,
isOptionSelfVoted(poll, index),
null
)
adapter?.list?.add(pollResultItem)
}
} else {
Log.e(TAG, "failed to get data to show poll results")
}
}
private fun isOptionSelfVoted(poll: Poll, index: Int): Boolean {
return poll.votedSelf?.contains(index) == true
}
private fun initEditButton(showEditButton: Boolean) {
if (showEditButton) {
_binding?.editVoteButton?.visibility = View.VISIBLE
@ -161,8 +121,8 @@ class PollResultsFragment(
}
}
override fun onClick(pollResultItem: PollResultItem) {
Log.d(TAG, "click..")
override fun onClick(pollResultHeaderItem: PollResultHeaderItem) {
viewModel.filterItems()
}
override fun onDestroyView() {

View File

@ -24,6 +24,10 @@ package com.nextcloud.talk.polls.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.nextcloud.talk.polls.adapters.PollResultHeaderItem
import com.nextcloud.talk.polls.adapters.PollResultItem
import com.nextcloud.talk.polls.adapters.PollResultVoterItem
import com.nextcloud.talk.polls.model.Poll
import com.nextcloud.talk.polls.repositories.PollRepository
import io.reactivex.disposables.Disposable
import javax.inject.Inject
@ -33,10 +37,20 @@ class PollResultsViewModel @Inject constructor(private val repository: PollRepos
sealed interface ViewState
object InitialState : ViewState
private var _poll: Poll? = null
val poll: Poll?
get() = _poll
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(InitialState)
val viewState: LiveData<ViewState>
get() = _viewState
private var _unfilteredItems: ArrayList<PollResultItem> = ArrayList()
private var _items: MutableLiveData<ArrayList<PollResultItem>> = MutableLiveData<ArrayList<PollResultItem>>()
val items: LiveData<ArrayList<PollResultItem>>
get() = _items
private var disposable: Disposable? = null
override fun onCleared() {
@ -44,6 +58,82 @@ class PollResultsViewModel @Inject constructor(private val repository: PollRepos
disposable?.dispose()
}
fun setPoll(poll: Poll) {
_poll = poll
initPollResults(_poll!!)
}
private fun initPollResults(poll: Poll) {
_items.value = ArrayList()
val votersAmount = getVotersAmount(poll)
val oneVoteInPercent = 100 / votersAmount
poll.options?.forEachIndexed { index, option ->
val votersAmountForThisOption = getVotersAmountForOption(poll, index)
val optionsPercent = oneVoteInPercent * votersAmountForThisOption
val pollResultHeaderItem = PollResultHeaderItem(
option,
optionsPercent,
isOptionSelfVoted(poll, index)
)
addToItems(pollResultHeaderItem)
val voters = poll.details?.filter { it.optionId == index }
if (!voters.isNullOrEmpty()) {
voters.forEach {
addToItems(PollResultVoterItem(it))
}
}
}
_unfilteredItems = _items.value?.let { ArrayList(it) }!!
}
private fun addToItems(pollResultItem: PollResultItem) {
val tempList = _items.value
tempList!!.add(pollResultItem)
_items.value = tempList
}
private fun getVotersAmount(poll: Poll): Int {
if (poll.details != null) {
return poll.details.size
} else if (poll.votes != null) {
return poll.numVoters
}
return 0
}
private fun getVotersAmountForOption(poll: Poll, index: Int): Int {
var votersAmountForThisOption: Int? = 0
if (poll.details != null) {
votersAmountForThisOption = poll.details.filter { it.optionId == index }.size
} else if (poll.votes != null) {
votersAmountForThisOption = poll.votes.filter { it.key.toInt() == index }[index.toString()]
if (votersAmountForThisOption == null) {
votersAmountForThisOption = 0
}
}
return votersAmountForThisOption!!
}
private fun isOptionSelfVoted(poll: Poll, index: Int): Boolean {
return poll.votedSelf?.contains(index) == true
}
fun filterItems() {
if (_items.value?.containsAll(_unfilteredItems) == true) {
val filteredList = _items.value?.filter { it.getViewType() == PollResultHeaderItem.VIEW_TYPE } as
MutableList<PollResultItem>
_items.value = ArrayList(filteredList)
} else {
_items.value = _unfilteredItems
}
}
companion object {
private val TAG = PollResultsViewModel::class.java.simpleName
}

View File

@ -34,7 +34,7 @@
android:id="@+id/poll_results_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:listitem="@layout/poll_result_item" />
tools:listitem="@layout/poll_result_header_item" />
</LinearLayout>
<com.google.android.material.button.MaterialButton

View File

@ -34,17 +34,7 @@
app:trackColor="@color/dialog_background"
app:trackCornerRadius="5dp"
app:trackThickness="5dp"
android:paddingBottom="@dimen/standard_half_padding"
tools:progress="50" />
<LinearLayout
android:id="@+id/poll_option_detail"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginBottom="4dp"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="@+id/poll_option_text"
app:layout_constraintTop_toBottomOf="@+id/poll_option_bar" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,25 @@
<LinearLayout 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:paddingBottom="4dp"
tools:background="@color/white">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/poll_voter_avatar"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="8dp"
android:layout_gravity="center"
app:roundAsCircle="true" />
<TextView
android:id="@+id/poll_voter_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
tools:text="Bill Murray" />
</LinearLayout>