update poll UI for moderators etc

- allow to end poll for moderators

- allow to see voters amount for voting screen for moderators and creators of the poll

- replace UserEntity with User

- show numVoters instead of votes

- minor refactoring

Signed-off-by: Marcel Hibbe <dev@mhibbe.de>
This commit is contained in:
Marcel Hibbe 2022-07-14 14:45:12 +02:00 committed by Andy Scherzinger (Rebase PR Action)
parent c848659fda
commit 40f20c56d6
11 changed files with 137 additions and 51 deletions

View File

@ -121,12 +121,14 @@ class IncomingPollMessageViewHolder(incomingView: View, payload: Any) : MessageH
if (pollId != null && pollName != null) { if (pollId != null && pollName != null) {
binding.messagePollTitle.text = pollName binding.messagePollTitle.text = pollName
val roomToken = (payload as? MessagePayload)!!.roomToken val roomToken = (payload as? MessagePayload)!!.currentConversation.token!!
val isOwnerOrModerator = (payload as? MessagePayload)!!.currentConversation.isParticipantOwnerOrModerator
binding.bubble.setOnClickListener { binding.bubble.setOnClickListener {
val pollVoteDialog = PollMainDialogFragment.newInstance( val pollVoteDialog = PollMainDialogFragment.newInstance(
message.activeUser!!, message.activeUser!!,
roomToken, roomToken,
isOwnerOrModerator,
pollId, pollId,
pollName pollName
) )

View File

@ -1,8 +1,9 @@
package com.nextcloud.talk.adapters.messages package com.nextcloud.talk.adapters.messages
import com.nextcloud.talk.models.json.conversations.Conversation
import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet import com.nextcloud.talk.ui.bottom.sheet.ProfileBottomSheet
data class MessagePayload( data class MessagePayload(
val roomToken: String, var currentConversation: Conversation,
val profileBottomSheet: ProfileBottomSheet val profileBottomSheet: ProfileBottomSheet
) )

View File

@ -137,12 +137,14 @@ class OutcomingPollMessageViewHolder(outcomingView: View, payload: Any) : Messag
if (pollId != null && pollName != null) { if (pollId != null && pollName != null) {
binding.messagePollTitle.text = pollName binding.messagePollTitle.text = pollName
val roomToken = (payload as? MessagePayload)!!.roomToken val roomToken = (payload as? MessagePayload)!!.currentConversation.token!!
val isOwnerOrModerator = (payload as? MessagePayload)!!.currentConversation.isParticipantOwnerOrModerator
binding.bubble.setOnClickListener { binding.bubble.setOnClickListener {
val pollVoteDialog = PollMainDialogFragment.newInstance( val pollVoteDialog = PollMainDialogFragment.newInstance(
message.activeUser!!, message.activeUser!!,
roomToken, roomToken,
isOwnerOrModerator,
pollId, pollId,
pollName pollName
) )

View File

@ -487,7 +487,7 @@ class ChatController(args: Bundle) :
val messageHolders = MessageHolders() val messageHolders = MessageHolders()
val profileBottomSheet = ProfileBottomSheet(ncApi!!, conversationUser!!, router) val profileBottomSheet = ProfileBottomSheet(ncApi!!, conversationUser!!, router)
val payload = MessagePayload(roomToken!!, profileBottomSheet) val payload = MessagePayload(currentConversation!!, profileBottomSheet)
messageHolders.setIncomingTextConfig( messageHolders.setIncomingTextConfig(
MagicIncomingTextMessageViewHolder::class.java, MagicIncomingTextMessageViewHolder::class.java,

View File

@ -26,14 +26,14 @@ import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.drawee.interfaces.DraweeController import com.facebook.drawee.interfaces.DraweeController
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.PollResultVoterItemBinding import com.nextcloud.talk.databinding.PollResultVoterItemBinding
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.polls.model.PollDetails import com.nextcloud.talk.polls.model.PollDetails
import com.nextcloud.talk.utils.ApiUtils import com.nextcloud.talk.utils.ApiUtils
import com.nextcloud.talk.utils.DisplayUtils import com.nextcloud.talk.utils.DisplayUtils
class PollResultVoterViewHolder( class PollResultVoterViewHolder(
private val user: UserEntity, private val user: User,
override val binding: PollResultVoterItemBinding override val binding: PollResultVoterItemBinding
) : PollResultViewHolder(binding) { ) : PollResultViewHolder(binding) {
@ -60,7 +60,7 @@ class PollResultVoterViewHolder(
displayName, displayName,
false false
), ),
null user
) )
) )
.build() .build()
@ -74,7 +74,7 @@ class PollResultVoterViewHolder(
pollDetail.actorId, pollDetail.actorId,
false false
), ),
null user
) )
) )
.build() .build()

View File

@ -23,12 +23,12 @@ package com.nextcloud.talk.polls.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.PollResultHeaderItemBinding import com.nextcloud.talk.databinding.PollResultHeaderItemBinding
import com.nextcloud.talk.databinding.PollResultVoterItemBinding import com.nextcloud.talk.databinding.PollResultVoterItemBinding
import com.nextcloud.talk.models.database.UserEntity
class PollResultsAdapter( class PollResultsAdapter(
private val user: UserEntity, private val user: User,
private val clickListener: PollResultItemClickListener, private val clickListener: PollResultItemClickListener,
) : RecyclerView.Adapter<PollResultViewHolder>() { ) : RecyclerView.Adapter<PollResultViewHolder>() {
internal var list: MutableList<PollResultItem> = ArrayList() internal var list: MutableList<PollResultItem> = ArrayList()

View File

@ -32,17 +32,17 @@ import androidx.lifecycle.ViewModelProvider
import autodagger.AutoInjector import autodagger.AutoInjector
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.DialogPollMainBinding import com.nextcloud.talk.databinding.DialogPollMainBinding
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.polls.model.Poll
import com.nextcloud.talk.polls.viewmodels.PollMainViewModel import com.nextcloud.talk.polls.viewmodels.PollMainViewModel
import javax.inject.Inject import javax.inject.Inject
@AutoInjector(NextcloudTalkApplication::class) @AutoInjector(NextcloudTalkApplication::class)
class PollMainDialogFragment : DialogFragment() { class PollMainDialogFragment : DialogFragment() {
lateinit var user: UserEntity lateinit var user: User
lateinit var roomToken: String lateinit var roomToken: String
private var isOwnerOrModerator: Boolean = false
lateinit var pollId: String lateinit var pollId: String
lateinit var pollTitle: String lateinit var pollTitle: String
@ -60,6 +60,7 @@ class PollMainDialogFragment : DialogFragment() {
user = arguments?.getParcelable(KEY_USER_ENTITY)!! user = arguments?.getParcelable(KEY_USER_ENTITY)!!
roomToken = arguments?.getString(KEY_ROOM_TOKEN)!! roomToken = arguments?.getString(KEY_ROOM_TOKEN)!!
isOwnerOrModerator = arguments?.getBoolean(KEY_OWNER_OR_MODERATOR)!!
pollId = arguments?.getString(KEY_POLL_ID)!! pollId = arguments?.getString(KEY_POLL_ID)!!
pollTitle = arguments?.getString(KEY_POLL_TITLE)!! pollTitle = arguments?.getString(KEY_POLL_TITLE)!!
} }
@ -83,19 +84,28 @@ class PollMainDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewModel.setIsOwnerOrModerator(isOwnerOrModerator)
viewModel.viewState.observe(viewLifecycleOwner) { state -> viewModel.viewState.observe(viewLifecycleOwner) { state ->
when (state) { when (state) {
PollMainViewModel.InitialState -> {} PollMainViewModel.InitialState -> {}
is PollMainViewModel.PollVoteHiddenState -> { is PollMainViewModel.PollVoteHiddenState -> {
binding.pollDetailsText.visibility = View.VISIBLE binding.pollVotedHidden.visibility = View.VISIBLE
binding.pollDetailsText.text = context?.resources?.getString(R.string.polls_private_voted) initVotersAmount(state.showVotersAmount, state.poll.numVoters, false)
showVoteScreen() showVoteScreen()
} }
is PollMainViewModel.PollVoteState -> { is PollMainViewModel.PollVoteState -> {
binding.pollDetailsText.visibility = View.GONE binding.pollVotedHidden.visibility = View.GONE
initVotersAmount(state.showVotersAmount, state.poll.numVoters, false)
showVoteScreen() showVoteScreen()
} }
is PollMainViewModel.PollResultState -> showResultsScreen(state.poll) is PollMainViewModel.PollResultState -> {
binding.pollVotedHidden.visibility = View.GONE
initVotersAmount(state.showVotersAmount, state.poll.numVoters, true)
showResultsScreen()
}
else -> {}
} }
} }
@ -103,7 +113,6 @@ class PollMainDialogFragment : DialogFragment() {
} }
private fun showVoteScreen() { private fun showVoteScreen() {
val contentFragment = PollVoteFragment.newInstance( val contentFragment = PollVoteFragment.newInstance(
roomToken, roomToken,
pollId pollId
@ -114,9 +123,7 @@ class PollMainDialogFragment : DialogFragment() {
transaction.commit() transaction.commit()
} }
private fun showResultsScreen(poll: Poll) { private fun showResultsScreen() {
initVotesAmount(poll.totalVotes)
val contentFragment = PollResultsFragment.newInstance( val contentFragment = PollResultsFragment.newInstance(
user user
) )
@ -126,12 +133,24 @@ class PollMainDialogFragment : DialogFragment() {
transaction.commit() transaction.commit()
} }
private fun initVotesAmount(totalVotes: Int) { private fun initVotersAmount(showVotersAmount: Boolean, numVoters: Int, showResultSubtitle: Boolean) {
binding.pollDetailsText.visibility = View.VISIBLE if (showVotersAmount) {
binding.pollDetailsText.text = String.format( binding.pollVotesAmount.visibility = View.VISIBLE
resources.getString(R.string.polls_amount_voters), binding.pollVotesAmount.text = String.format(
totalVotes resources.getString(R.string.polls_amount_voters),
) numVoters
)
} else {
binding.pollVotesAmount.visibility = View.GONE
}
if (showResultSubtitle) {
binding.pollResultsSubtitle.visibility = View.VISIBLE
binding.pollResultsSubtitleSeperator.visibility = View.VISIBLE
} else {
binding.pollResultsSubtitle.visibility = View.GONE
binding.pollResultsSubtitleSeperator.visibility = View.GONE
}
} }
/** /**
@ -140,19 +159,22 @@ class PollMainDialogFragment : DialogFragment() {
companion object { companion object {
private const val KEY_USER_ENTITY = "keyUserEntity" private const val KEY_USER_ENTITY = "keyUserEntity"
private const val KEY_ROOM_TOKEN = "keyRoomToken" private const val KEY_ROOM_TOKEN = "keyRoomToken"
private const val KEY_OWNER_OR_MODERATOR = "keyIsOwnerOrModerator"
private const val KEY_POLL_ID = "keyPollId" private const val KEY_POLL_ID = "keyPollId"
private const val KEY_POLL_TITLE = "keyPollTitle" private const val KEY_POLL_TITLE = "keyPollTitle"
@JvmStatic @JvmStatic
fun newInstance( fun newInstance(
user: UserEntity, user: User,
roomTokenParam: String, roomTokenParam: String,
isOwnerOrModerator: Boolean,
pollId: String, pollId: String,
name: String name: String
): PollMainDialogFragment { ): PollMainDialogFragment {
val args = Bundle() val args = Bundle()
args.putParcelable(KEY_USER_ENTITY, user) args.putParcelable(KEY_USER_ENTITY, user)
args.putString(KEY_ROOM_TOKEN, roomTokenParam) args.putString(KEY_ROOM_TOKEN, roomTokenParam)
args.putBoolean(KEY_OWNER_OR_MODERATOR, isOwnerOrModerator)
args.putString(KEY_POLL_ID, pollId) args.putString(KEY_POLL_ID, pollId)
args.putString(KEY_POLL_TITLE, name) args.putString(KEY_POLL_TITLE, name)
val fragment = PollMainDialogFragment() val fragment = PollMainDialogFragment()

View File

@ -33,8 +33,8 @@ import androidx.recyclerview.widget.LinearLayoutManager
import autodagger.AutoInjector import autodagger.AutoInjector
import com.nextcloud.talk.R import com.nextcloud.talk.R
import com.nextcloud.talk.application.NextcloudTalkApplication import com.nextcloud.talk.application.NextcloudTalkApplication
import com.nextcloud.talk.data.user.model.User
import com.nextcloud.talk.databinding.DialogPollResultsBinding import com.nextcloud.talk.databinding.DialogPollResultsBinding
import com.nextcloud.talk.models.database.UserEntity
import com.nextcloud.talk.polls.adapters.PollResultHeaderItem import com.nextcloud.talk.polls.adapters.PollResultHeaderItem
import com.nextcloud.talk.polls.adapters.PollResultItemClickListener import com.nextcloud.talk.polls.adapters.PollResultItemClickListener
import com.nextcloud.talk.polls.adapters.PollResultsAdapter import com.nextcloud.talk.polls.adapters.PollResultsAdapter
@ -51,7 +51,7 @@ class PollResultsFragment : Fragment(), PollResultItemClickListener {
private lateinit var parentViewModel: PollMainViewModel private lateinit var parentViewModel: PollMainViewModel
lateinit var viewModel: PollResultsViewModel lateinit var viewModel: PollResultsViewModel
lateinit var user: UserEntity lateinit var user: User
lateinit var binding: DialogPollResultsBinding lateinit var binding: DialogPollResultsBinding
@ -108,7 +108,7 @@ class PollResultsFragment : Fragment(), PollResultItemClickListener {
if (showEditButton) { if (showEditButton) {
binding.editVoteButton.visibility = View.VISIBLE binding.editVoteButton.visibility = View.VISIBLE
binding.editVoteButton.setOnClickListener { binding.editVoteButton.setOnClickListener {
parentViewModel.edit() parentViewModel.editVotes()
} }
} else { } else {
binding.editVoteButton.visibility = View.GONE binding.editVoteButton.visibility = View.GONE
@ -142,7 +142,7 @@ class PollResultsFragment : Fragment(), PollResultItemClickListener {
@JvmStatic @JvmStatic
fun newInstance( fun newInstance(
user: UserEntity user: User
): PollResultsFragment { ): PollResultsFragment {
val args = Bundle() val args = Bundle()
args.putParcelable(KEY_USER_ENTITY, user) args.putParcelable(KEY_USER_ENTITY, user)

View File

@ -52,24 +52,29 @@ class PollMainViewModel @Inject constructor(private val repository: PollReposito
private lateinit var roomToken: String private lateinit var roomToken: String
private lateinit var pollId: String private lateinit var pollId: String
private var editPoll: Boolean = false private var isOwnerOrModerator: Boolean = false
private var editVotes: Boolean = false
sealed interface ViewState sealed interface ViewState
object InitialState : ViewState object InitialState : ViewState
open class PollVoteState( open class PollVoteState(
val poll: Poll, val poll: Poll,
val showVotersAmount: Boolean,
val showEndPollButton: Boolean val showEndPollButton: Boolean
) : ViewState ) : ViewState
open class PollVoteHiddenState( open class PollVoteHiddenState(
val poll: Poll, val poll: Poll,
val showVotersAmount: Boolean,
val showEndPollButton: Boolean val showEndPollButton: Boolean
) : ViewState ) : ViewState
open class PollResultState( open class PollResultState(
val poll: Poll, val poll: Poll,
val showEditButton: Boolean, val showVotersAmount: Boolean,
val showEndPollButton: Boolean val showEndPollButton: Boolean,
val showEditButton: Boolean
) : ViewState ) : ViewState
private val _viewState: MutableLiveData<ViewState> = MutableLiveData(InitialState) private val _viewState: MutableLiveData<ViewState> = MutableLiveData(InitialState)
@ -89,8 +94,8 @@ class PollMainViewModel @Inject constructor(private val repository: PollReposito
loadPoll() loadPoll()
} }
fun edit() { fun editVotes() {
editPoll = true editVotes = true
loadPoll() loadPoll()
} }
@ -130,28 +135,34 @@ class PollMainViewModel @Inject constructor(private val repository: PollReposito
} }
override fun onComplete() { override fun onComplete() {
val showEndPollButton = poll.status == Poll.STATUS_OPEN && isPollCreatedByCurrentUser(poll) val showEndPollButton = showEndPollButton(poll)
val showVotersAmount = showVotersAmount(poll)
if (votedForOpenHiddenPoll(poll)) { if (votedForOpenHiddenPoll(poll)) {
_viewState.value = PollVoteHiddenState(poll, showEndPollButton) _viewState.value = PollVoteHiddenState(poll, showVotersAmount, showEndPollButton)
} else if (editPoll && poll.status == Poll.STATUS_OPEN) { } else if (editVotes && poll.status == Poll.STATUS_OPEN) {
_viewState.value = PollVoteState(poll, showEndPollButton) _viewState.value = PollVoteState(poll, false, showEndPollButton)
editPoll = false editVotes = false
} else if (poll.status == Poll.STATUS_CLOSED || poll.votedSelf?.isNotEmpty() == true) { } else if (poll.status == Poll.STATUS_CLOSED || poll.votedSelf?.isNotEmpty() == true) {
setPollResultState(poll) val showEditButton = poll.status == Poll.STATUS_OPEN && poll.resultMode == Poll.RESULT_MODE_PUBLIC
_viewState.value = PollResultState(poll, showVotersAmount, showEndPollButton, showEditButton)
} else if (poll.votedSelf.isNullOrEmpty()) { } else if (poll.votedSelf.isNullOrEmpty()) {
_viewState.value = PollVoteState(poll, showEndPollButton) _viewState.value = PollVoteState(poll, showVotersAmount, showEndPollButton)
} else { } else {
Log.w(TAG, "unknown poll state") Log.w(TAG, "unknown poll state")
} }
} }
} }
private fun setPollResultState(poll: Poll) { private fun showEndPollButton(poll: Poll): Boolean {
val showEditButton = poll.status == Poll.STATUS_OPEN && poll.resultMode == Poll.RESULT_MODE_PUBLIC return poll.status == Poll.STATUS_OPEN && (isPollCreatedByCurrentUser(poll) || isOwnerOrModerator)
val showEndPollButton = poll.status == Poll.STATUS_OPEN && isPollCreatedByCurrentUser(poll) }
_viewState.value = PollResultState(poll, showEditButton, showEndPollButton) private fun showVotersAmount(poll: Poll): Boolean {
return votedForPublicPoll(poll) ||
poll.status == Poll.STATUS_CLOSED ||
isOwnerOrModerator ||
isPollCreatedByCurrentUser(poll)
} }
private fun votedForOpenHiddenPoll(poll: Poll): Boolean { private fun votedForOpenHiddenPoll(poll: Poll): Boolean {
@ -160,10 +171,19 @@ class PollMainViewModel @Inject constructor(private val repository: PollReposito
poll.votedSelf?.isNotEmpty() == true poll.votedSelf?.isNotEmpty() == true
} }
private fun votedForPublicPoll(poll: Poll): Boolean {
return poll.resultMode == Poll.RESULT_MODE_PUBLIC &&
poll.votedSelf?.isNotEmpty() == true
}
private fun isPollCreatedByCurrentUser(poll: Poll): Boolean { private fun isPollCreatedByCurrentUser(poll: Poll): Boolean {
return userUtils.currentUser?.userId == poll.actorId return userUtils.currentUser?.userId == poll.actorId
} }
fun setIsOwnerOrModerator(ownerOrModerator: Boolean) {
isOwnerOrModerator = ownerOrModerator
}
companion object { companion object {
private val TAG = PollMainViewModel::class.java.simpleName private val TAG = PollMainViewModel::class.java.simpleName
} }

View File

@ -49,13 +49,51 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/poll_results_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textColor="@color/low_emphasis_text"
android:text="@string/polls_results_subtitle"
android:visibility="gone"
tools:visibility="visible"/>
<TextView
android:id="@+id/poll_results_subtitle_seperator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textColor="@color/low_emphasis_text"
android:text=" - "
android:visibility="gone"
tools:visibility="visible"
tools:ignore="HardcodedText" />
<TextView
android:id="@+id/poll_votes_amount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textColor="@color/low_emphasis_text"
tools:text="93 votes" />
</LinearLayout>
<TextView <TextView
android:id="@+id/poll_details_text" android:id="@+id/poll_voted_hidden"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:textColor="@color/low_emphasis_text" android:textColor="@color/low_emphasis_text"
tools:text="Poll results - 93 votes" /> android:text="@string/polls_private_voted" />
<FrameLayout <FrameLayout
android:id="@+id/message_poll_content_fragment" android:id="@+id/message_poll_content_fragment"

View File

@ -537,12 +537,13 @@
<string name="message_search_begin_empty">No search results</string> <string name="message_search_begin_empty">No search results</string>
<!-- Polls --> <!-- Polls -->
<string name="polls_amount_voters">Poll results - %1$s votes</string> <string name="polls_amount_voters">%1$s voters</string>
<string name="polls_add_option">Add Option</string> <string name="polls_add_option">Add Option</string>
<string name="polls_private_voted">You successfully voted for this private poll.</string> <string name="polls_private_voted">You successfully voted for this private poll.</string>
<string name="polls_end_poll">End Poll</string> <string name="polls_end_poll">End Poll</string>
<string name="polls_end_poll_confirm">Do you really want to end this poll? This can\'t be undone.</string> <string name="polls_end_poll_confirm">Do you really want to end this poll? This can\'t be undone.</string>
<string name="polls_max_votes_reached">You can\'t vote with more options for this poll.</string> <string name="polls_max_votes_reached">You can\'t vote with more options for this poll.</string>
<string name="polls_results_subtitle">Results</string>
<string name="title_attachments">Attachments</string> <string name="title_attachments">Attachments</string>